原题链接
简洁描述:
对一个序列给定两种操作,交换两个相邻的数,求经过 k k k轮冒泡排序后逆序对个数。一轮冒泡排序指的是冒泡排序内层循环执行一遍。
分析:
可以发现,一个数能进行交换,当且仅当前面没有比它大的数。而且每轮前面比它大的数都会少 1 1 1,因为前面有且仅有一个最大的数到它的后面,其他较大且能交换的数一定遇到这个最大的数停止交换。所以一个数在第几轮被放在正确位置就看它前面数的个数。
再来研究交换。每次交换如果 a < b a<b a<b,则逆序对加一,左边的数比它大的数加一。如果 a > b a>b a>b,则逆序对减一,右边的数比它大的数减一。
再发现,想知道某一轮的逆序对个数那么就是总的逆序对个数减去它前面每轮交换个数即可。每轮交换的个数就是 n − n- n−放正确的个数。因为没放正确的都会和一个放正确大的做交换,所以就是求一个前缀和,要求前缀和用树状数组啊!把第一个设为总的逆序对,后面的都是这一轮少的逆序对个数。交换一次就更新一下变化的数。因为我们要在求逆序对的同时求出前面比它大的个数,就刚好用树状数组求很好。不知道怎么求的,参考我写的树状数组统计。
细节问题:因为最开始我们在全部的逆序对进行修改,这样后面的所有的逆序对个数都有了变化。那么什么时候变化要还原呢?过了交换之后的轮数就和原来是一样的了。因为到了那个轮数把它们原来该交换的地方交换了要么就换回去,要么就原序列换成新序列,所以在该轮结束后要还原逆序对。
时间复杂度 Θ ( m log n ) \Theta(m\log n) Θ(mlogn)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int NN=2e5+5;
int n,m,a[NN],b[NN],num[NN];
long long c[NN];
int lowbit(int x)
{
return x&-x;
}
void add(int x,long long sum)
{
while(x<=n)
{
c[x]+=sum;
x+=lowbit(x);
}
}
long long find(int x)
{
long long res=0;
while(x>0)
{
res+=c[x];
x-=lowbit(x);
}
return res;
}
int main()
{
scanf("%d%d",&n,&m);
long long res=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=i-1-find(a[i]);
++num[b[i]];
res+=b[i];
add(a[i],1);
}
memset(c,0,sizeof(c));
add(1,res);
int t=0;
for(int i=1;i<=n;i++)
{
t+=num[i-1];
add(i+1,-(n-t));
}
for(int i=1;i<=m;i++)
{
int opt,x;
scanf("%d%d",&opt,&x);
x=min(x,n-1);
if(opt==1)
{
if(a[x]<a[x+1])
{
swap(a[x],a[x+1]);
swap(b[x],b[x+1]);
add(1,1);
b[x+1]++;
add(b[x+1]+1,-1);
}
else
{
swap(a[x],a[x+1]);
swap(b[x],b[x+1]);
add(1,-1);
add(b[x]+1,1);
b[x]--;
}
}
else
printf("%lld\n",find(x+1));
}
return 0;
}