【题解】NOIO2020 冒泡排序(树状数组)

【题解】NOIO2020 冒泡排序(树状数组)

考虑在k次冒泡后的排列中的一个逆序对\(i<j,a_i>a_j\)

因为这个\(a_i\)的存在,意味着\(a_j\)没有被往右边交换过(否则不会存在一个\(a_i>a_j\))。

对于每个没有被交换到右边的数,他前面总共有\(k\)个比他大的数被交换走了。记\(ans[i]\)表示原序列\(i\)位置和之前的数构成的逆序对个数,那么\(k\)次交换后,对答案的贡献是\(ans[i]-k\)

那么答案就是

\[\sum \max(0,\text{ans}[i]-k) \]

\(ans[i]\)的值域开两个树状数组,每次取所有大于\(k\)的所有\(ans\)之和,再减去\(ans[i]>k\)的个数乘以\(k\)就是答案。两个操作都是取一段后缀,直接树状数组即可。

至于交换操作,由于只影响了两个\(ans\),所以直接模拟一遍就行。

复杂度\(O(n\log n)\)。注意\(k\)要对\(n-1\)取min。

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;  typedef long long ll;  
inline int qr(){
	int ret=0,f=0,c=getchar();
	while(!isdigit(c)) f|=c==45,c=getchar();
	while( isdigit(c)) ret=ret*10+c-48,c=getchar();
	return f?-ret:ret;
}
const int maxn=2e5+5;
int data[maxn],n,m;
ll seg1[maxn],seg2[maxn];
void add(ll*seg,int pos,ll val){
	++pos;
	for(int t=pos;t<=n+1;t+=t&-t)
		seg[t]+=val;
}
ll que(ll*seg,int pos){
	++pos;
	ll ret=0;
	for(int t=pos;t>0;t-=t&-t)
		ret+=seg[t];
	return ret;
}
ll que(ll*seg,int l,int r){return que(seg,r)-que(seg,l-1);}
int ans[maxn];
void del(int pos){add(seg1,ans[pos],-ans[pos]); add(seg2,ans[pos],-1);}
void add(int pos){add(seg1,ans[pos],ans[pos]); add(seg2,ans[pos],1);}

int main(){
	n=qr(); m=qr();
	for(int t=1;t<=n;++t)
		data[t]=qr(),ans[t]=t-1-que(seg1,data[t]),add(seg1,data[t],1);
	memset(seg1,0,sizeof seg1);
	for(int t=1;t<=n;++t) add(t);
	for(int t=1;t<=m;++t){
		int op=qr(),x=min(qr(),n);
		if(op==1){
			del(x),del(x+1);
			ans[x+1]-=data[x]>data[x+1];
			swap(data[x],data[x+1]); swap(ans[x],ans[x+1]);
			ans[x+1]+=data[x]>data[x+1];
			add(x),add(x+1);
		}else{
			ll ret=que(seg1,x,n)-que(seg2,x,n)*x;
			printf("%lld\n",ret);
		}
	}
	return 0;
}

posted @ 2020-03-07 14:21  谁是鸽王  阅读(273)  评论(0编辑  收藏  举报