[CCO2020]Exercise Deadlines(贪心)

题面

给一个长度为(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);
}