【洛谷P6186】[NOI Online #1 提高组] 冒泡排序

前言

【题目传送门】
个人一直觉得用两个树状数组/线段树维护信息的题都很巧妙(因为我想不到),这题正好就是。
而且本身也有一点思维含量。

题解

先从冒泡排序的那个询问入手,考虑一轮排序过后有什么影响。
直观感受是每一个大值会一路往后走,直到遇到更大的值,而且每一轮都至少排好一个数。但是感觉这都没太大用啊!
回顾逆序对的统计,是对于每个数看它前面有多少个大于它的数字,再仔细观察就会发现每一轮排序过后,每个数前面比他大的数都只会减少固定的值:一个。
记录 \(f_i\) 表示 \(i\) 位置的数字前面有多少个大于它的数,那么初始逆序对的答案就是 \(\sum f_i\)
那么 \(k\) 轮排序之后的答案就是所有大于 \(k\)\(f_i\) 之和减去 \(k\times\) (大于 \(k\)\(f_i\) 个数)。
这两个都可以用权值线段树或者树状数组记录,然而我写的时候分类讨论炸了,还是看了题解才讨论对...

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define FCC fclose(stdin),fclose(stdout)
const int INF = 0x3f3f3f3f,N = 2e5+10;
inline ll read()
{
	ll ret=0;char ch=' ',c=getchar();
	while(!(c>='0'&&c<='9')) ch=c,c=getchar();
	while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
	return ch=='-'?-ret:ret;
}
ll f[N],a[N];
int n,m;
struct BIT
{
	ll c[N];
	inline void update(int x,int v)
	{
		if(!x) return; 
		for(int i=x;i<=n;i+=i&-i) c[i]+=v;
	}
	inline ll query(int x)
	{
		ll ret=0;
		for(int i=x;i;i-=i&-i) ret+=c[i];
		return ret;
	}

}tr1,tr2;
//tr1求初始逆序对,后来求值域上面的个数,tr2求值域的答案。
//(上面这句话貌似只有我自己能看懂) 
void pretreat()
{
	for(int i=1;i<=n;i++)	
	{
		tr1.update(a[i],1);
		f[i]=tr1.query(n)-tr1.query(a[i]);
		//printf("f[%d]=%d\n",i,f[i]);
		tr2.update(f[i],f[i]);
	}
	memset(tr1.c,0,sizeof(tr1.c)); //tr1回收利用 
	for(int i=1;i<=n;i++) tr1.update(f[i],1);
}
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	pretreat();
	for(int i=1;i<=m;i++)	
	{
		int op=read(),x=read();
		if(op==1)
		{
			tr1.update(f[x],-1),tr2.update(f[x],-f[x]);
			tr1.update(f[x+1],-1),tr2.update(f[x+1],-f[x+1]);
			swap(f[x],f[x+1]);
			if(a[x]>a[x+1]) f[x]--;
			else f[x+1]++;
			swap(a[x],a[x+1]);
			tr1.update(f[x],1),tr2.update(f[x],f[x]);
			tr1.update(f[x+1],1),tr2.update(f[x+1],f[x+1]);
		}
		else 
		{
			if(x>=n) {puts("0");continue;}
			int cnt=tr1.query(n)-tr1.query(x);
			printf("%lld\n",tr2.query(n)-tr2.query(x)-1ll*cnt*x);
		}
	}
	return 0;
}
posted @ 2021-11-07 15:42  conprour  阅读(48)  评论(0编辑  收藏  举报