[NOI Online #1 提高组] 冒泡排序题解

题目

三个点:

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 nx

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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值