题目
三个点:
1. 利用桶来实现计算逆序对数量
树状数组1 作桶 求逆序对
2. 计算每次冒泡排序,序列的总逆序对数量会减少多少
树状数组2 记序列的逆序对值 k k k 从 1 − > n 1->n 1−>n 的差分
由于当数 x 1 > x 2 x1>x2 x1>x2 时, x 1 x1 x1 与 x 2 x2 x2 进行交换会使 x 2 x2 x2 的逆序对数量减一,
所以每次冒泡如果当前有 x x x 个可以同自己右边的数进行交换的数,所有
同自己左边的数交换 的数的逆序数都会减一,逆序对就减少了 n − x n - x n−x 个
3. 判断一个数和另一个数交换,对排序后的逆序数变化的影响
比如 x 1 < x 2 x1<x2 x1<x2 , x 1 x1 x1的逆序数为 a a a, x 2 x2 x2的逆序数为 b b b
首先可知, a a a会加一, b b b不变
因为在 a + 1 a+1 a+1轮冒泡中, x 2 x2 x2才会和 x 1 x1 x1交换
所以交换后,第 a + 1 a+1 a+1轮后的冒泡结果不会发生改变
而 a + 1 a+1 a+1轮之前的冒泡结果中逆序对数量会加一
所以在差分数组中,只需序列的总逆序对数量加一,和 a a a上的值减一就可满足上述条件.
#include<bits/stdc++.h>
#define lowbit(a) (a&(-a))
using namespace std;
const int N = 2e5+10;
typedef long long ll;
int a[N],before[N],record[N];
int n,m;
ll tree[N];
void add(int x,ll v){
for(int i = x;i<=n;i+=lowbit(i))tree[i] += v;
}
ll query(int x){
ll res = 0;
for(int i = x;i>=1;i-=lowbit(i))res += tree[i];
return res;
}
int main(){
scanf("%d%d",&n,&m);
ll tot = 0;//先用于记序列的总逆序对数
for(int i= 0;i<n;++i){
scanf("%d",&a[i]);
before[i] = i - query(a[i]);//before用于记录每一个数的逆序对数量
tot += before[i];
record[before[i]]++;//桶,record[i]用于记录每种逆序对数量有多少个
add(a[i],1); //树状数组作桶
}
memset(tree, 0, sizeof(tree)); //清用于下方建立差分的树状数组
add(1,tot);//实现差分,先把序列总逆序对数量放在最前面
tot = 0;
for(int i = 0;i<n;++i){
tot += record[i];//tot记录record数组前缀和
add(i + 2, -(n - tot));//实现差分
//下标问题,i要+2
}
for(int i = 0,op,x;i<m;++i){
scanf("%d%d",&op,&x);
x = min(x, n - 1);
if(op == 1){
x--;
if(a[x] < a[x + 1]){
swap(a[x],a[x + 1]);
swap(before[x],before[x + 1]);
add(1,1);//逆序对总数加1
add(before[x + 1] + 2, -1);
before[x + 1]++;
}
else{
swap(a[x], a[x + 1]);
swap(before[x], before[x + 1]);
add(1, -1);//逆序对总数量减1
before[x]--;
add(before[x] + 2,1);
}
}
else printf("%lld\n",query(x + 1));
}
return 0;
}