似乎MM都很喜欢拼图游戏。如果MM过生日你不知道送她什么,送她一副拼图是一个不错的选择(事实上原来我也曾干过这事)。如果你失恋了,或者挂科了,或者这个月没饭钱了,或者怀疑自己的性取向,感到很郁闷的时候,静下心来玩一玩拼图游戏可以让你暂时忘掉烦恼。当你最终完成整个拼图时,你会有前所未有的成就感。当然,只有那些有耐心的人才觉得拼图有趣,像我这样的人肯定拼个十几二十分钟就觉得烦了。计算机搞久了的人往往都很没耐心,同一个操作反复执行的次数多了就觉得很烦,心里总会想这种机械操作交给傻B计算机去做该多省事啊。有时我会想,计算机是否有什么牛B算法可以用来解决拼图问题。今天我们要研究的是,如何把拼图游戏描述成一个信息学问题,计算机是否有更高效的算法来解决这个问题。
传统的拼图一共有w*h个正方形小块,最终将拼成一个w*h的矩形图案。我们大致有以下两种依据来确定一个小块的位置:根据这一小块上的图案来确定它在整幅图片中的位置,或者从形状上观察这一小块可以和其它哪些块拼接。于是,拼图游戏变成了这样一种交互式的问题:允许你询问某一块是否在指定的位置,或者某两块是否相连,你如何尽早地完成整个拼图。具体地说,你可以:
- 询问拼块A是否在(x,y)上,交互库返回yes/no
- 询问拼块A和拼块B是否相连,交互库返回yes/no
有时候,你并不能把拼图完全当作一个顶点最大度为4的无向图。多数情况下两个拼块只能按某一个方向上的某一种顺序相连。为了更贴近拼图游戏的真实情况,我们可以假定,对于第二个问题如果返回的是yes,则交互库还会告诉你A应该接在B的什么方向。现在的问题是,完成整个拼图最少需要多少次询问?
假如拼图共有n块,询问的次数不会超过O(n^2)。对于每一个拼块,我都像傻B一样挨着挨着询问“它是不是在这里”,O(n^2)次询问可以保证我完成整副拼图。我们希望知道,是否有算法可以使用O(nlogn)甚至更少的询问次数?
答案是否定的。对于拼图问题,计算机并没有英明到哪里去,它也只能像傻B一样一个一个去试。我们下面将证明,不管你怎么努力,询问次数再怎么也不会低于O(n^2)。首先我们需要说明的是,问题2实际上并不能带给我们多大的帮助。
如上图,我们把整个拼图划分成一个一个的“十字架”,并且挖掉每个十字架正中间的那个格子(深灰色的格子)。注意到关于这种划分的三个重要性质:
- 每个浅灰色的格子最多与一个深灰色的格子相邻
- 任何两个深灰色的格子都不相邻
- 深灰色的格子共有n/5个(可能有常数级别的偏差)
现在,假如整个拼图里只剩这些被挖掉的深灰色格子还没确定,其它的格子上都已经放好了正确的拼块。再换句话说,在拼图游戏过程中,拼块是否应放在浅灰色的格子里,若可以则应该放在哪个格子,以及浅灰色格子之间的邻接状态都是已经知道的了,只要是不涉及深灰色格子的信息,你要什么我就给你什么。此时,我们只剩下n/5个格子(仍然是O(n)个格子),并且询问1与询问2变得完全等价;你要问拼块A和拼块B是否相邻,还不如直接问拼块是否应放在某个洞里。于是,问题变为这样,只凭借询问1来确定O(n)个拼块的位置需要多少次询问。我们下面证明,O(n^2)次询问是必须的。
考虑一个二分图,左边n个顶点表示n个拼块,右边n个顶点表示拼图上残留的n个洞。现在,我只能询问指定的两顶点间是否有边,只有当交互库回答了n次yes后拼图才算完成。那么,作为交互库,你应该尽可能返回对游戏者不利的信息,让整个局面往最坏的方向发展。如果叫你来写这个交互库,你该怎么写?容易想到,只要有可能,我都返回no;除非某个时候一旦我再返回一次no,所有没被问过的边和返回过yes的边所组成的二分图不存在一个完全匹配时,我才可能返回yes。我们需要一个二分图存在完全匹配的充分条件来支持我们的这个算法。
考虑如下定理:如果一个二分图左边右边各有n个顶点,每个顶点都与对面至少n/2个顶点相连,则这个二分图一定存在一个完全匹配。定理的证明很简单。König定理告诉我们,二分图的最大匹配数应该等于最小点覆盖集,而一个图的最小点覆盖与最大点独立集是互补的,它们的和始终等于顶点数|V|(在这里|V|=2n)。因此我们只需要证明,上述二分图的最大点独立集不会超过n。假如我在左边选的顶点数不超过n/2个,则右边最多也只能选n/2个顶点(左边任一个点都已经使右边至少n/2个点废了);假如我左边选的顶点数超过了n/2个,则右边的顶点一个都不能选(右边每个点都连接了左边至少n/2个点,任选一个都会导致冲突)。总之,最大点独立集不可能超过n,但n显然是可以达到的(取同一边的所有点),那么最小点覆盖集也就是n,即二分图存在完全匹配。
有了这个定理,下面我就好办了:任何时候,只要每个顶点你都有半数以上的边没问过,我就可以放心大胆的回答no(因为这些没问过的边总可以组成一个完全匹配);一旦某个时刻有一个顶点被问过了n/2次,那么我就随便找一个完全匹配,把这个点“亮”出来,告诉你这个点应该和哪个点匹配(不计询问次数),然后把这两个匹配了的顶点从图中删去,继续刚才的操作。每次删除一对顶点都会顺带着删掉与它们相连的至少k/2条问过的边,其中k表示当时左边右边各剩下k个顶点。删掉了多少边就表示你曾问过了多少边,因此完成整个拼图你总共问过至少n/2 + (n-1)/2 + … + 2/2 + 1/2条边,这个数量显然是O(n^2)的。
做人要厚道
转贴请注明出处
参考资料:http://www.brand.site.co.il/riddles/200710q.html