博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

信息学中“序”的浅析

Posted on 2010-10-22 21:45  桃子在路上  阅读(316)  评论(0编辑  收藏  举报

关键词

信息学 序 排序 线段树 预处理 时间复杂度

引言

时间是“序”,程序的某次执行有唯一确定“序”,预处理时经常要排“序”,解题的思考过程本身有一个自然的“序”,动态规划需要找到有“序”的阶段,递推要有推的一个顺“序”,非线性数据结构往往需要通过遍历来确定一个“序”……
信息学和“序”有着非常紧密的联系,相互交织在一起。在学习信息学的过程中,很多时候都是在寻找隐藏在问题背后的、非常重要的“序”。
本文想通过一系列具体的实例来简单地阐述“序”和问题本身的关系,以及“序”在解题中扮演的角色。同时,还通过将这些例子串联在一起,提出如何研究信息学、开展教学的方法,希望对广大教师设计奥赛辅导专题有一些启发。
“序”经常给解决问题带来好处,但有时也会产生负面影响,文章也会就如何消除“序”的不利之处举例。
正如前面所提到的,“序”的应用实在太广,所以本文无法全部涉及到,只是大海捞针似地,举一些耳熟能详的例子。不过,只要做到能举一反三,就能够在竞赛教学和实践中运用自如。

正文

一、快速排序

谈到“序”,排序往往是大家第一时间想到的。没错,作为预处理很小一部分的排序相当有用。所以这里先来回顾一下快速排序,由此也引出了后续的讨论。
快速排序是一种大家耳熟能详的排序方法,随机化快速排序的平均时间复杂度是 的,已经达到了通过比较来进行排序的时间复杂度下限。因此,它是一种在竞赛中被广泛使用的排序方法。
在学习这种算法的时候,关键是要抓住它的本质:分治。
用分治法解决一个问题,一般有三个步骤:
(a)分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
(b)解决:如果子问题规模较小且容易被直接解决,则直接解,否则递归地用分治法解各个子问题;
(c)合并:将各个子问题的解合并为原问题的解。
在快速排序中,如果要对 到 之间的元素排序,就先通过若干次交换操作,使得序列能够分解成两个部分 到 和 到 ,且满足第一部分中的任意一个元素都不超过第二部分中的任意一个元素(假设从小到大排序)。分解好以后,对这两个部分分别排序。合并操作在快速排序中不需要进行,因为两个部分排序完毕以后,当前要排的这一整段也就排好序了。特殊情况,或者说递归求解的边界条件是待排序的只有一个元素了,此时就不用继续分治了。
那么快速排序就只有排序这一个用途吗?有没有什么可以挖掘呢?用它的这种分治法能解决其他的问题吗?我们在竞赛辅导的过程中,就经常要问学生这种问题,这样才能使他们的思维活跃起来。而不只是为了解决一个特定的问题而学一种算法,学算法是为了解决一类问题,一类本质相似的问题。即使这个算法不能解决某个问题,也能带给你不少的联想和思路,对解决其他问题到来帮助。

二、排成无序的“快速排序”

让我们来看这个问题:在n个数中求第k小的数。
有了快速排序,就可以在 的时间内对它们排序,然后在 时间里即可找到第k大的元素。但这种方法并不好,这就是前面讲的:学习快速排序不仅仅是为了排序。
这里只是要求一下第k小的这一个元素,如果将整个序列都排序的话,很明显有些“画蛇添足”了。于是,我们可以引导学生自己设计出类似于快速排序的一种算法,减少不必要的排序量,在 时间内求出问题的解。
例如,在 到 中求第k小的元素,按照快速排序的分解方式分解后,可以得出第k小的元素在左边第一部分,还是在右边第二部分。如果是在左边,那么就接下来就在 到 中找第k小的元素;如果在右边,就接下来就要在 到 中找第k+p-q-1小的元素。这样做时间复杂度低的原因在于每次只对上次的一半元素进行处理,只要观察两个指针每次移动的距离总和,就能得出复杂度为 。
类似的例子还有一些,例如用归并排序来求逆序对( )等。
如果在某个问题中,有C个这样的询问:在n个数中,第k小的数是什么?那又该如何处理呢?问题就是这样被拓展出来的,一定要让学生养成这样的习惯。这样才能从一个问题引伸出更值得思考的东西。很明显,如果询问次数C大于 次,那么当然先排序再用 的时间回答每个问题比较合算;否则,就每次用 的时间回答问题。这些问题的本质并没有改变。

三、改变问题处理的顺序

将前面的问题稍稍改变一下:对于一个n个数的序列,如果有q个问题,每个问题是问一个序列的前p个数中最k小的元素。沿用前面的方法,同样可以设计出 的算法,但当nq都比较大的时候,效率就比较低了。
如果先用整个序列的所有元素构造一棵较为平衡的查找树(例如伸展树等),再依次回答每个问题可谓一无是处。此时就应该考虑处理询问顺序的问题。问题是事先给出的,回答要按问题的顺序来答,但并不表示处理的时候就一定要依次进行。
所以,可以先对问题排序,然后依次往查找树中插入新节点,当然还要存储子树节点个数信息。加了第p个数的时候,如果正好有问题问前p个中第k小的,那么在当前的查找树中查找一下第k小的元素。这样,插入所有元素,构造整棵树的时间复杂度为O(nlogn),回答一个问题 ,对问题序列排序需要 时间。因此,总的时间复杂度为 。
当n个数序列中数的范围不太大的时候,在对问题序列排序后,还可以使用线段(区间)树来帮助求解。数值作为区间中的点,树中每一个节点存储它所表示区间的当前点数。这样做的时间复杂度是 ,这里的I表示数值的范围。当然,如果范围大到了无法接受的地步,那也可以先排个序,然后用下标来暂时“取代”原先的值,再用线段树来处理。

四、最大(小)值与堆

基于刚才提出的这种改变处理顺序的思想,还可以变形为一种特殊的情况:对于一个n个数的序列,如果有q个问题,每个问题是问一个序列的前p个数中最小的元素。
一提到最小、最大,很自然的联想到使用堆。由于问题已经排序了,那么就可以一边插入元素到堆里面,一边回答问题。
但是此时不同的问题个数至多有n种,所以可以在插入每一个元素后把当前最小值都记录下来,然后直接 时间回答即可,可省去对询问排序的时间。总的时间复杂度为 。

五、排序创造处理的顺序

前面的几个问题,对于优秀的选手来说,都是很常规的,并不困难。做这些题对他们的帮助并不大。这就要求我们在设计问题的时候,要想方设法给他们设置障碍,增加附加的限制条件,使得算法不容易一下子直接得到,或者说要转个弯甚至转几个弯才能得出。
例如Balkan OI 2004的一题的降维以后的版本:BOI(国际半岛信息学奥林匹克)要选拔最优秀的学生代表巴尔干半岛去参加IOI。有N名优秀学生参加选拔赛,从1到N编号, 。考试一共两次,没有任意两名考生在任意一次考试中名次相同。
如果选手A的2次考试名次都在选手B前面,就称A“优于”B
如果对于选手A,没有其他的选手“优于”A,就称A是“优秀”的。
请求出“优秀”的选手数。
可以将每名选手两次的成绩看成一个整体,先按照第一次考试的排名从小到大排序。以这个顺序作为以后处理的顺序。在判断每名选手是否优秀的时候,其实就是要判断:在按照第一次考试的排名从小到大排好序的序列中,他前面有没有选手在第二次考试中考的比他好,即值更小。例如用 表示p在第二次考试中的排名,那么要判断p是否优秀,只要判断 是否是 到 中最小的,即只要比较 和前p-1个数的最小值即可。此问题就是在第四部分提到的,算法可以照搬,总时间复杂度为 。
 
有时只需要考虑序,而不需要太多地考虑原先的值。例如要求两个数列ab(最大长度为50000)的最长公共子序列,而且每个数在一个序列中至多出现一次。
我们知道,使用一般的动态规划算法,最好也只能做到 的时间复杂度。但此问题有个特殊性,就是每个数在一个序列中至多出现一次,也就是规定了配对。那么就可以有一种高效的算法,只关心配对两个数的“序”,排序后进行处理。
先根据第二个数列从前往后处理,每次访问到 的时候,直接( )找出 在第一个数列中出现的位置t。接着就是要求b数组下标小于ia数组下标小于t的元素的最长公共子序列的长度。b数组下标小于i可以通过插入操作来保证,关键就是每次要求a数组下标小于t的元素的某一个数值的最大值。要完成这样的操作,可以使用线段树等高效的数据结构,每次插入和询问区间最大值都是 的,总的时间复杂度就将为 。

六、设置障碍,假设规定一部分处理的顺序

读者不难发现,前面好多算法都是基于某种特定的顺序来进行处理的,即先排序,再按顺序处理。那么如果我们可以附加限制条件,使得无法排序,处理的顺序必须按照我们给出的顺序进行,那么算法就会发生很大的变化。例如上题,如果有某种限制,限制了不能按照第一次考试的排名从小到大排序后再处理,那又该怎么办呢?
算法实际上并不难,只要维护一棵线段树即可。将第一次的名次作为线段树表示区间中的坐标,将第二次的名次作为线段树叶节点的值,每个非叶节点表示的区间存放一个最小值。先用 的时间将所有元素都放入线段树,再用 的时间来一一判断所有选手是否优秀。也是只要求出某个区间内的最小值即可,所以构造线段树的时候,每个节点要存子树中的最小值。

七、设置障碍,真正规定一部分处理的顺序,并增加条件

那么到底可以怎么限制学生不能排序呢?Balkan OI 2004的原题就可以给我们一些启发:只要在加一次竞赛,即总共3次竞赛排名。这样的话,势必要对其中的一次竞赛来排序,这样另外两次竞赛处理的顺序也就固定了,目的自然达到。
BOI(国际半岛信息学奥林匹克)要选拔最优秀的学生代表巴尔干半岛去参加IOI。有N名优秀学生参加选拔赛,从1到N编号, 。考试一共三次,没有任意两名考生在任意一次考试中名次相同。
如果选手A的3次考试名次都在选手B前面,就称A“优于”B
如果对于选手A,没有其他的选手“优于”A,就称A是“优秀”的。
请求出“优秀”的选手数。
 
可以先按照第一次考试的排名作为在线段树中插入和查询的顺序,以第二次考试的排名作为线段树表示区间中的坐标,以第三次的名次作为线段树叶节点的值,每个非叶节点表示的区间存放一个当前线段树中的最小值。
然后再按照第一次考试的排名从先往后,每次插入一个人x的第二次排名 和第三次排名 ,然后询问区间 中的最小值 。如果 ,就说明x不是“优秀”的,因为bx前被插入线段树,三次排名都比x好;如果 ,则说明x是“优秀”,因为前两次竞赛排名比x好的所有选手在第三次竞赛中的排名都没有x好。
每个插入和询问操作都在 时间内完成,所以总的时间复杂度是 。

八、使用交互式来规定一部分处理的顺序

在近几年的竞赛中,有一种题型是交互式题型,交互的特点就是有时间先后顺序,所以可以利用这个特点,来限制数据处理的顺序。而且,目前求较优解的问题变得越来越多,交互式在这一方面也有用武之地。
再来看第三部分提出的那个问题:对于一个n个数的序列,如果有q个问题,每个问题是问一个序列的前p个数中最k小的元素。但是现在讲这个问题变成交互式的,要求每问一个问题就要马上回答,回答后才问下一个问题。
这样一来,前面介绍的先将问题排序,再依次处理算法就无效了,之前提到的唯一可行算法就是每次用 的时间回答一个问题:求前p个数中第k小的数(修改快速排序的方法)。但是当nq如果都比较大( )时,此方法就不够高效了。
先来看一个想法,它本身并不好,但能优化。这是从插入排序得到启发的,将插入排序每次插入一个新元素后的结果都存下来。这样就得到了n个数组,第i个数组存储的是前i个数排好序的结果。每次插入新的元素,操作就和插入排序时完全一样。这样的预处理需要 的时间,空间也是 ,回答每个问题只要 的时间,总体来说并不理想。
但考虑到预处理的时间复杂度太高,回答每个问题的时间复杂度又比较低,那么有没有折中的方案呢?有,就是分块。将n个元素分成若干组,为了使效率较高,这里可以将它们分成 组,每组约 个元素。每次“插入”排序的时候,将一整组全体插入并排序,可以先对这一组进行快速排序,再合并两段递增的数列,每次的时间复杂度为 。由于一共只有 组,所以这样预处理的总时间复杂度为 ,空间也是 。
回答问题的时候,先找到一个合适的预处理后数组(数组排好了t个元素,t是小于或等于的p的最大值),然后在这个数组和以后的p-t个元素中找第k大的。每次回答的时间复杂度为 。
由于两方面作了折中,总的时间复杂度降为 ,算法对于比较大的nq可以胜任。

九、消除“序”带来的不利影响

正如前面讲过的,出题者有时会使用“序”来增加问题的复杂性,模糊我们的“视线”。此时,我们有两种策略:一个就是前面介绍的,没有办法改变“处理的顺序”,只能根据问题的本质,设计更高效的算法,使用更高效的数据结构;另一个就是想办法消除这个“序”,通过预处理,使得前后处理变得相互无关。
例如有这样一个问题:
尼克在一家养猪场工作,这家养猪场共有M间锁起来的猪舍,由于猪舍的钥匙都给了客户(N位客户),所以尼克没有办法打开这些猪舍,客户们从早上开始一个接一个来购买生猪,他们到达后首先用手中的钥匙打开他所能打开的全部猪舍,然后从中选取他要买的生猪,尼克可以在此期间将打开的猪舍中的猪调整到其它开着的猪舍中,每个猪舍能存放的猪的数量是没有任何限制的。买完猪后客户会将他打开的猪舍关上。
好在尼克事先知道每位客户手中有哪些钥匙,要买多少猪,以及客户到来的先后次序。请你写一个程序,帮助尼克求出最多能卖出多少头生猪。
 
本题的模型和网络流的最大流很像,但有一个特殊性,就是每次被打开的房间内的猪可以相互移动,这就是一个“序”。为了解决这个问题,不妨换个角度来思考:实际上如果某客户取的猪是以前从其他的房间过来的,那么是不是可以就让这个客户到它原先所在的房间去取呢?即猪本身并不移动,而是钥匙重新分配,即客户拥有之前来的客户的某些钥匙。
例如客户1有3和4的钥匙,客户2有4和5的钥匙,那么客户2就等价拥有3、4和5这3把钥匙。又如果客户3有5和6的钥匙,他又等价拥有3、4、5和6的钥匙。
也就是说:只要某客户拥有的钥匙和之前的某些客户(他们的钥匙也有可能是别人给的)有交集,那么那些客户就相当于把他们的钥匙都给了这个客户。
这样按照顺序预处理后,客户前后到来的有序限制和猪可以移动的不利因素就不存在了,构图后求最大流即可。构图方式是:图中的点有客户和房间,增加源点和汇点,源点到房间连边,容量为房内猪的数量;客户到汇点连边,容量为客户的需求;房间到客户,只要客户有该房间的钥匙就连边,容量为无穷大。
由于这个图比较特殊,实现算法前还可以适当简化后处理。

总结

信息学中有许多有序和无序的事物,这些事物都能够成为我们解题的好帮手。在教学过程中,时时刻刻都应该指导学生去体会这种“序”,学会去欣赏和利用“序”这个非常优美的概念。人们对于任何事物的认识,都是从无知到有知的一个循序渐进的过程,关于“序”的更深入的研究势必会在中学生中被广泛进行。

参考资料

1、   《算法导论(第二版)》
2、   UVA题库
3、   Balkan OI 2004试题
4、   中国国家集训队论文