关于“逆序数”

昨天我们做了清华的预选赛,沈大、梁老大、肖叉各搞定一道题,险些跌出60名。我做了B和F,其中F是关于逆序数的题目,复杂度是 nlog2n+mn 最差的复杂度可能降为O(n^2)。但我提交的结果不是TLE,而是MLE和RE。真不知道是清华判题系统有问题还是我的程序有问题。总之,我心有不服啊,所以决定今天花点时间归纳一下“逆序对”的题目,给大家写份报告,提供点资料。 首先,逆序对(inversion pair)是指在序列{a0,a1,a2...an}中,若aij),则(ai,aj)上一对逆序对。而逆序数(inversion number)顾名思义就是序列中逆序对的个数。例如: 1 2 3是顺序,则逆序数是0;1 3 2中(2,3)满足逆序对的条件,所以逆序数只有1; 3 2 1中(1,2)(1,3)(2,3)满足逆序对,所以逆序是3。由定义不能想象,序列n的逆序数范围在[0,n*(n-1)/2],其中顺序时逆序数为0,完全逆序时逆序数是n*(n-1)/2。

目前我知道的求逆序最快的适合ACM/ICPC的算法是归并排序时计算逆序个数,时间复杂度是nlog2n,而空间复杂度2n。JAVA模板(服务器是校内的)。

归并求逆序简单原理:
归并排序是分治的思想,具体原理自己去看书吧。利用归并求逆序是指在对子序列 s1和s2在归并时,若s1[i]>s2[j](逆序状况),则逆序数加上s1.length-i,因为s1中i后面的数字对于s2[j]都是逆序的。

TJU 2242:
直接上模板,记得m的奇偶要考虑的哦。

PKU 1007:
求逆序数,然后排序输出就行了。

PKU 1804, PKU 2299:
是最简单的关于逆序对的题目,题目大意是给出一个序列,求最少移动多少步可能使它顺序,规定只能相邻移动。
相邻移动的话,假设a b 相邻,若ab时,交换会减少逆序,使序列更顺序,所以做交换。
由上可知,所谓的移动只有一种情况,即a>b,且一次移动的结果是逆序减1。假设初始逆序是n,每次移动减1,那么就需要n次移动时序列变为顺序。所以题目转化为直接求序列的逆序便可以了。

ZJU 1481:
这题和本次预选赛的F略有相似,不过要简单得多。题意是给定序列s,然后依次将序列首项移至序列尾,这样共有n-1次操作便回到了原序列(操作类似于循环左移)。问这n-1次操作和原序列,他们的逆序数最小的一次是多少?
有模板在手,直观地可以想到是,对于这n次都求逆序数,然后输出最小的一次就可以了,但这样做的复杂度有O(n*nlogn),太过复杂。
如果只求初始序列的逆序数的话,只要后面的n-1次操作的逆序数能够在O(1)的算法下求得,就能保证总体O(nlogn)的复杂度了。事实上,对于每次操作确实可以用O(1)的算法求得逆序数。将序列中ai移到aj的后面,就是ai做j-i次与右邻的交换,而每次交换有三个结果:逆序+1、逆序-1、逆序不变。由于题目中说明序列中无相同项,所以逆序不变可以忽略。逆序的加减是看ai与aj间(包括aj)的数字大小关系,所以求出ai与aj间大于ai的数字个数和小于ai的数字个数然后取差,就是ai移动到aj后面所导致的逆序值变化了。
依据上面的道理,因为题目有要求ai是移动到最后一个数,而ai又必定是头项,所以只要计算大于ai的个数和小于ai的个数之差就行了。然后每次对于前一次的逆序数加上这个差,就是经过这次操作后的逆序数值了。

PKU 2086:
这题不是求逆序对,而是知道逆序数k来制造一个序列。要求序列最小,两个序列比较大小是自左向右依次比较项,拥有较大项的序列大。
其实造序列并不难,由1804可知,只要对相邻数做调整就能做到某个逆序数了。难点是在求最小的序列。举例 1 2 3 4 5,要求逆序1的最小序列是交换4 5,如果交换其他任意相邻数都无法保证最小。由此可以想到,要保证序列最小,前部分序列可以不动(因为他们已经是最小的了),只改动后半部分。而我们知道n个数的最大逆序数是n*(n-1)/2,所以可以求一个最小的p,使得 k考虑k=7,n=6的情况,求得p=5,即前部分1不动,后面5个数字调整。4个数的最大逆序是5 4 3 2,逆序数是6,5个数是6 5 4 3 2,逆序数是10。可以猜想到,保证5中4个数的逆序不动,调整另一个数的位置就可以增加或减少逆序数,这样就能调整出6-10间的任意逆序。为了保证最小,我们可以取尽量小的数前移到最左的位置就行了。2前移后逆序调整4,3前移后调整了3,4调整2,5调整1,不动是调整0,可以通过这样调整得到出6-10,所以规律就是找到需要调整的数,剩下的部分就逆序输出。需要调整的数可以通过总逆序k-(p-1)*(p-2)/2+(n-p)求得。

PKU 1455:
这是一道比较难的关于逆序数推理的题目,题目要求是n人组成一个环,求做相邻交换的操作最少多少次可以使每个人左右的邻居互换,即原先左边的到右边去,原右边的去左边。容易想到的是给n个人编号,从1..n,那么初始态是1..n然后n右边是1,目标态是n..1,n左边是1。
初步看上去好象结果就是求下逆序(n*(n-1)/2 ?),但是难点是此题的序列是一个环。在环的情况下,可以减少许多次移动。先从非环的情况思考,原1-n的序列要转化成n-1的序列,就是做n(n-1)/2次操作。因为是环,所以(k)..1,n..k+1也可以算是目标态。例如:1 2 3 4 5 6的目标可以是 6 5 4 3 2 1,也可以是 4 3 2 1 6 5。所以,问题可以转化为求形如(k)..1,n..k+1的目标态中k取何值时,逆序数最小。
经过上面的步骤,问题已经和ZJU1481类似的。但其实,还是有规律可循的。对于某k,他的逆序数是左边的逆序数+右边的逆序数,也就是(k*(k-1)/2)+((n-k)*(n-k-1)/2) (k>=1 && k<=n)。展开一下,可以求得k等于n/2时逆序数最小为((n*n-n)/2),现在把k代入进去就可以得到解了。
要注意的是k是整数,n/2不一定是整数,所以公式还有修改的余地,可以通用地改为(n/2)*(n-1)/2。

PKU 2893:
用到了求逆序数的思想,但针对题目还有优化,可见M*N PUZZLE的优化。

PKU 1077:
比较经典的搜索题,但在判断无解的情况下,逆序数帮了大忙,可见八数码实验报告。

 

你可能感兴趣的:(ACM/ICPC)