luogu6186 冒泡排序 题解

分析

手算一下不难发现,一轮冒泡排序会让所有逆序对个数大于 1 的位置减少 1 个逆序对,逆序对为 0 的则不受影响。

\(f_i\) 表示位置 \(i\) 的逆序对数,那么经过 \(k\) 轮冒泡排序后,逆序对的个数为 \[ \sum_{ i=1 \text{ and } f_i > k}^n f_i - k \cdot \sum_{i \in [1,n]} [f_i > k] \] 树状数组维护之,具体见代码。交换的操作分类讨论就行了。

CODE

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=2e5+5;
int n, m, a[N];
ll sum, f[N];
struct BIT {
	ll c[N];
	void reset() { memset(c,0,sizeof(c)); }
	void modify(ll x,ll y) {
		if(!x) return;
		// 放置下标为0
		for(;x<=n;x+=x&-x) c[x]+=y;
	}
	ll query(ll x) {
		ll y=0;
		for(;x;x-=x&-x) y+=c[x];
		return y;
	}
} t1, t2;
void pre() {
	for(int i=1;i<=n;++i) {
		t1.modify(a[i],1);
		f[i]=t1.query(n)-t1.query(a[i]);
//		printf("%lld\n",f[i]);
		t2.modify(f[i],f[i]);
		
	}
    // 先利用t1求出原本的逆序对,再维护f[i]的权值数列
    // t2维护t1对应的和
	t1.reset();
	for(int i=1;i<=n;++i) t1.modify(f[i],1);
}
int main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	
	pre();
	while(m--) {
		int op, x; scanf("%d%d",&op,&x);
		if(op&1) {
			t1.modify(f[x],-1), t1.modify(f[x+1],-1);
			t2.modify(f[x],-f[x]), t2.modify(f[x+1],-f[x+1]);
            // 把交换的这两个数先从树状数组中删了
			if(a[x]>a[x+1]) --f[x+1]; else if(a[x]<a[x+1]) ++f[x];
            // a[x]>a[x+1],交换后x+1这个位置的逆序会减少1
            // 反之,x这个位置逆序对会增加1。
			swap(f[x],f[x+1]), swap(a[x],a[x+1]);
            // 交换他们的值
			t1.modify(f[x],1), t1.modify(f[x+1],1);
			t2.modify(f[x],f[x]), t2.modify(f[x+1],f[x+1]);
            // 重新插入回去
		} else {
			if(x>=n) { puts("0"); continue; }
			int cnt=t1.query(n)-t1.query(x);
			printf("%lld\n",t2.query(n)-t2.query(x)-1ll*cnt*x);
            // 注意1ll*cnt
		}
	}
}

luogu6186 冒泡排序 题解
https://yozora0908.github.io/2022/lg6186-solution/
作者
yozora0908
发布于
2022年5月1日
许可协议