题面
给一个长度为(n)的序列(d_i(d_i leq n)),求满足(i leq d_{p_i})的排列(p)中逆序对数最小值
分析
一开始我们令(p_i=i),从后往前调整,并计算每一个任务移到当前位置需要的交换次数。贪心考虑(d_i)较大的任务对位置限制较松,应该放到最后面,把任务按照(d_i)排列,按照(d_i)从大到小贪心。又因为要逆序对数最小,应该把(i)较大的放到更后面。于是需要一个优先队列存储任务,并倒序一个一个加到排列里。对于每个任务,交换的次数(=)当前要放到的位置-任务当前在排列里的位置. 这样就得到了(mathcal{O}(n^2))的做法,可以得到70分
考虑优化,算法的瓶颈在维护每个数当前在排列里的位置。类似[IOI2019]排列鞋子,用树状数组维护编号为(i)的任务的位置(p_i)。初始时把树状数组中每一位设成1,移动的时候将对应位-1,那么前缀和就是在当前在(i)前面任务的个数,就得到了(i)的位置.时间复杂度(mathcal{O}(nlog n))
代码
//这第4个题不讲武德,来骗,来偷袭,这好吗?这不好!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#define maxn 200000
using namespace std;
int n;
struct tree{
int c[maxn+5];
inline int lowbit(int x){
return x&(-x);
}
void add(int x,int v){
for(int i=x;i<=n;i+=lowbit(i)) c[i]+=v;
}
int query(int x){
int ans=0;
for(int i=x;i>=1;i-=lowbit(i)) ans+=c[i];
return ans;
}
}T;
int d[maxn+5];
vector<int>id[maxn+5];
priority_queue<int>q;
int main(){
#ifndef LOCAL
freopen("ddl.in","r",stdin);
freopen("ddl.out","w",stdout);
#endif
// freopen("d1p2.2-36.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&d[i]);
id[d[i]].push_back(i);
}
for(int i=1;i<=n;i++) T.add(i,1);
long long ans=0;
for(int i=n;i>=1;i--){
for(int j=0;j<(int)id[i].size();j++) q.push(id[i][j]);
if(q.empty()){
ans=-1;
break;
}
int x=q.top();
q.pop();
ans+=i-T.query(x);
T.add(x,-1);
}
printf("%lld
",ans);
}