【洛谷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;
}