P6186 [NOI Online #1 提高组] 冒泡排序 的 个人见解

作为小白的作者,看了佬们的解析后依然困惑,所以写了这份菜鸟也能看懂的超详细解析,可能有一些表达错误,勿喷

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

代码借用一位大佬的,注释是我写的

#include <bits/stdc++.h>
using namespace std;

int input[200005], before[200005], record[200005];
int n;

long long tree[200005];
inline int lowbit(int x)
{
    return x & (-x);
}
void add(int x, long long val)
{
    for (; x <= n; x += lowbit(x))
        tree[x] += val;
    return;
}
long long sum(int x)
{
    long long res = 0;
    for (; x > 0; x -= lowbit(x))
        res += tree[x];
    return res;
}

int main()
{
    int m;
    scanf("%d%d", &n, &m);
    long long tot = 0; //先用于记录序列的总逆序对数量
    for (int i = 0; i < n; i++)
    {
        scanf("%d", input + i);
        before[i] = i - sum(input[i]); //before用于记录每一个数的逆序对数量
        tot += before[i];
        record[before[i]]++;		   //桶,record[i]用于记录每种逆序对数量有多少个
        add(input[i], 1);	   //树状数组作桶
    }
    memset(tree, 0, sizeof(tree)); //清空,用于下方建立差分的树状数组
    add(1, tot);				   //实现差分,先把序列总逆序对数量放在最前面
    tot = 0;                           //此处开始tot不再记录序列的总逆序对数量
    //通过分析可知,第i轮冒泡排序,逆序总数t会减去n-x(x为record数组的i-1项的前缀和,n为input的长度即数字个数)
    for (int i = 0; i < n; ++i)
    {
        tot += record[i];             //tot记录的是record数组的前缀和
        add(i + 2, -(n - tot)); //实现差分,每一个单点表示,第i轮的冒泡排序后的逆序对数量
        //由于下标问题,i必须+2,这样当i=0时就会储存在第2位,而第1位是放总逆序对数的
    }
    for (int i = 0, opt, x; i < m; i++)
    {
        scanf("%d%d", &opt, &x);
        x = min(x, n - 1); //对opt=2的情况进行优化
        if (opt == 1)
        {
            x--;
            if (input[x] < input[x + 1])
            {
                swap(input[x], input[x + 1]);
                swap(before[x], before[x + 1]);
                add(1, 1);					//逆序对总数量增加1
                add(before[x + 1] + 2, -1); //由于before[x+1]增加了,所以在原before[x+1]轮时也可以使逆序对-1,所以记录-1
                before[x + 1]++;			//input[x]交换到x+1位上后,前面比它大的数量增加了
            }
            else
            {
                swap(input[x], input[x + 1]);
                swap(before[x], before[x + 1]);
                add(1, -1);			   //逆序对总数量减少1
                before[x]--;		   //input[x+1]交换到x位上后,前面的比它大的数量减少了1
                add(before[x] + 2, 1); //由于before[x]减少了,所以在原before[x]轮时无法使逆序对减少,所以记录1
            }
        }
        else
            printf("%lld\n", sum(x + 1)); //直接输出答案
    }
    return 0;
}

这道题有三个点,弄清楚了,就简单了

1、利用桶来实现计算逆序对数量:

每输入一个数x,树状数组tree就会在x的位置加1,每个数的逆序数为 该数字前的数字的个数 减去 数字前比该数字小的数字的个数,数字前比该数字小的数字的个数 又等于 数组中 该数字位置的前缀和。

2、如何计算每次冒泡排序,序列的总逆序对数量会减少多少:

例子:4 3 5 2 1

那么这个序列进行一次冒泡之后是这个样子的。

第一轮后:3 4 2 1 5

我们发现,4与3交换,5与2与1交换,但2却没有和1交换,所以一个数可以同自己右边的数进行交换当且仅当左边没有比它大的数(说明这个数的逆序数为0),这个就是冒泡排序的本质。

由于当数x1>x2时,x1与x2进行交换会使x2的逆序对数量减一,所以每次冒泡如果当前有 x 个可以同自己右边的数进行交换的数,所有 同自己左边的数交换 的数的逆序数都会减一,逆序对就减少了 n−x 个

由此也可知,当一个数前面有y 个比它大的数的话(逆序对数量为y),这个数会在第 y+1 轮冒泡中达到同自己右边数交换的条件。

3、判断一个数和另一个数交换,对排序后的逆序数变化的影响

比如x1<x2,x1的逆序数为a,x2的逆序数为b(a显然等于b)

首先可知,a会加一,b不变。

因为在a+1轮冒泡中,x2才会和x1交换

所以交换后,第a+1轮后的冒泡结果不会发生改变

而a+1轮之前的冒泡结果中逆序对数量会加一

所以在差分数组中,只需序列的总逆序对数量加一,和 a上的值减一就可满足上述条件。

不知道你看懂了没有

没有就算了

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值