我发现找规律越来越难了(考场上找规律方向与正解恰好相反的我)
# 题面
见洛谷 P6186……
# 解析
Tab. 下面记输入的原数组为
num[]
因为逆序对只会被考虑一次(比如 $(i,j)$ 是逆序对,那 $(j,i)$ 也是,但是只考虑其中一个),不妨对于位置 $i$,只考虑它前面的比它大的位置 $j$,设 val[i]
表示位置 $i$ 前面有 val[i]
个大于 num[i]
的数。
那么考虑进行一轮冒泡排序后 val[]
会怎么变化——
实际上,可以把冒泡排序的过程看成下面这样一些移动操作:
首先,被移动的位置肯定 val[i]
是等于 0 的,也就是说它前面的数都比它小。否则,如果前面有比它大的数,那么那个数移动时就会跳过 i
(也就是说 i
作为了上图中蓝色部分)。而且非常显然的是,因为 num[]
是一个 1 到 n 的排列,那么如果 val[i]==0
,那么 i
一定会被移动。
另外,我们知道冒泡排序后,val[]
只会变小——除非 val[i]==0
。
那么考虑 val[i]>0
的情况,对应的就是上图蓝色部分。我们发现,一次冒泡排序后,恰好有一个在它前面的比它大的数被挪到它后面去了,那么 val[i]--
。
综合一下:一轮冒泡排序后,①如果 val[i]==0
,则 val[i]
不变;②如果 val[i]>0
,则 val[i]--
。于是可以得到,经过 $k$ 轮冒泡排序后,val[i]=max(0,val[i]-k)
。
那么我们可以这样回答询问“进行 $k$ 轮冒泡排序”:找到大于(大于等于也一样) $k$ 的所有 val[i]
的和记为 sum
、以及有多少个这样的 val[i]
记为 cnt
——那么答案就是 sum-k*cnt
。这个简单,预处理一下 val[]
($O(n\log n)$ 逆序对),然后把 val[i]
前缀和(把权值作为下标来前缀和,类似于权值线段树)一下就好了~
但是还有修改操作。其实不难,假如要交换 $x,x+1$,显然 $x$ 之前或 $x+1$ 之后的位置的 val[]
没有影响。于是分类讨论(下面的 num[x]
和 num[x+1]
指交换过后对应的值):
num[x]>num[x+1]
:那么val[x+1]
比原来增加 1;num[x]<num[x+1]
:那么val[x]
比原来减少 1;
> Hint
这里务必注意
val[i]
的定义是“在i
之前的比num[i]
大的位置的个数”
我们发现,我们要求能够单点修改,查询前缀和……那不是树状数组就好了?
最后,还有一个坑,注意到查询的“进行$k$轮冒泡排序”中 $k$ 可以远大于 $n$,显然不能硬算,但是当 $k>n$ 时,整个数组已经排好了,那么就没有逆序对了。
# 源代码
1 | /*Lucky_Glass*/ |
# 赛场情况
我相当于是把 val[i]
定义为在 i
后面的比 num[i]
小的位置的数量……然后根本没发现任何规律——这种定义下,只有“移动”(就是文章中的图示中的“移动”了的位置)的 val[]
会减小,而且减小的值是移动的距离。
另外一个同学做出了正解,但是忽略了 $k>n$ 的这种情况。他写的权值线段树(实际上也可以),在这种情况下会死递归,然后就爆栈,洛谷的民间数据挂的很惨……不知道官方数据有没有可能。
The End
Thanks for reading!
“谁马踏桃花 画角长吟金鼓铿锵 寸土不让 万军一剑挡” - 《万古生香》