[NOI Online 提高组]冒泡排序

传送门

怎么说呢(咳咳),貌似是一道结论题(博主并不知道结论呀,所以就FST抱灵了呀)。好吧博主承认没看到\(k<2^{31}\)(相信没阿克的奆佬[除我]应该都RE在了这里)。测试成绩出来了,还好没有\(k\)极其之大的情况:)

先强调这里的\(\{p_i\}\)排列

仔细研读这个代码:

// 一轮交换
for i = 1 to n-1
    if p[i] > p[i + 1]
        swap(p[i], p[i + 1])

好像跟我刚学OI时的冒泡不一样耶

相邻的两项如果是逆序对就会进行交换。显然交换次数就是逆序对个数,不过这个没什么卯用。

再康康:发现如果一个数很大,比它后面的xx个数都要大,那么一顿排序猛如虎后,这个数会到达第一个比它大的数的前面,比如说\(\{\mathring4,1,2,3,5\}\),一轮排序后变成了\(\{1,2,3,\mathring4,5\}\)。计\(f_i\)表示数字\(i\)所在位置前面比它大的数,那么逆序对总数就是\(\sum f_i\)。这轮排序结束后,显然\(f_i\)只减不增,并且最多只会减一,就打这个例子,原本\(f_1=f_2=f_3=1\),之后就全变成了\(0\)。事实上由于前面最多有一个数字经过这个位置,且这个数字一定大于这个位置的数字,故最多只能使\(f_i\)减一,这个情况的发生必定有前面比它大的数字经过它。而又如果\(f_i>0\),则前面必定有比它大的数,其中一个数字一定会经过它,而且这个数字一定是前缀最大的数字(根据前文的推断,所有比这个数字小的都被“堵在”这个最大数字前面)。

把这些信息提炼出来就是:对于数字\(i\),排序过程中如果它所在位置前面有数字大于\(i\),那么一轮排序会使得前面最大的数字排到\(i\)的后面,这样\(f_i\)会减一。

综上,对于数字\(i\),其\(f_i\)\(k\)轮排序后会变成\(\max\{f_i-k,0\}\)

然后这道题就简单啦。维护\(f_i\),交换两个数相当于对\(f_i\)修改(本题的修改非常简单),对于查询,实际上就是求\(\sum\limits_{1\leqslant i\leqslant n}max\{f_i-k,0\}\),我们可以运用差分,配上数据结构维护关于\(k\)的信息:前缀\(f_i\)和前缀\(g_i=\sum\limits_{1\leqslant j\leqslant n}[f_j==i]\)\(g_i\)\(f_j==i\)的个数),然后查询答案对\(f_i\)进行差分再去掉\(g_i\)的差分乘上\(k\)即可。

复杂度\(\text{O}(n\log n)\)

为什么说\(k\)很坑?你如果做\(2^{31}-1\)次排序,再查查看\(k\)前缀(⊙o⊙)?!

posted @ 2020-03-07 22:38  AC-Evil  阅读(265)  评论(2编辑  收藏  举报