计算机与拼图游戏:探讨一个交互式问题

    似乎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

07年NOIp模拟赛by Matrix67 解题报告

Problem 1: Matrix67的情书(二)
    大家都知道,看一个数是否能被2整除只需要看它的个位能否被2整除即可。可是你想过为什么吗?这是因为10能被2整除,因此一个数10a+b能被2整除当且仅当b能被2整除。大家也知道,看一个数能否被3整除只需要看各位数之和是否能被3整除。这又是为什么呢?答案或多或少有些类似:因为10^n-1总能被3整除。2345可以写成2*(999+1) + 3*(99+1) + 4*(9+1) + 5,展开就是2*999+3*99+4*9 + 2+3+4+5。前面带了数字9的项肯定都能被3整除了,于是要看2345能否被3整除就只需要看2+3+4+5能否被3整除了。当然,这种技巧只能在10进制下使用,不过类似的结论可以推广到任意进制。
    注意到36是4的整数倍,而ZZZ…ZZ除以7总是得555…55。也就是说,判断一个36进制数能否被4整除只需要看它的个位,而一个36进制数能被7整除当且仅当各位数之和能被7整除。如果一个数同时能被4和7整除,那么这个数就一定能被28整除。于是问题转化为,有多少个连续句子满足各位数字和是7的倍数,同时最后一个数是4的倍数。这样,我们得到了一个O(n)的算法:用P[i]表示前若干个句子除以7的余数为i有多少种情况,扫描整篇文章并不断更新P数组。当某句话的最后一个字能被4整除时,假设以这句话结尾的前缀和除以7余x,则将此时P[x]的值累加到最后的输出结果中(两个前缀的数字和除以7余数相同,则较长的前缀多出来的部分一定整除7)。
    上述算法是我出这道题的本意,但比赛后我见到了其它各种各样新奇的算法。比如有人注意到36^n mod 28总是等于8,利用这个性质也可以构造出类似的线性算法来。还有人用动态规划(或者说递推)完美地解决了这个问题。我们用f[i,j]表示以句子i结束,除以28余数为j的文本片段有多少个;处理下一句话时我们需要对每一个不同的j进行一次扫描,把f[i-1,j]加进对应的f[i,j']中。最后输出所有的f[i,0]的总和即可。这个动态规划可以用滚动数组,因此它的空间同前面的算法一样也是常数的。
    如果你完全不知道我在说什么,你可以看看和进位制同余相关的文章。另外,我之前还曾出过一道很类似的题(VOJ1090),你可以对比着看一看。
    另外,非常抱歉地告诉大家,这道题的最后一个数据是错的。这个数据的第一个句子是一个空句子(感谢Ai.Freedom的提醒)。很多第一题只得了90分的好同志估计都是由于我的失误造成的,这里再次表示歉意。如果去掉最前面的那个问号,输出应该是19511110。

Problem 2: 送给MM的生日礼物
        
    我们用f[i,j,k]表示以第i行第j列的格子为右下角,边长为k的正方形是否符合要求。要想f[i,j,k]=True,首先必须满足f[i-1,j-1,k-2]为True(灰色部分满足要求)。另外,我们还需要保证图中两块蓝色区域相等,两块绿色区域相等,并且这四个区域自身还必须是对称的。由于动态规划的状态已经是三方的了,因此判断后面的这些条件必须在常数时间里完成。为此,我们可以在动态规划前进行以下6个预处理:

以同列不同行的两个格子(i,j)和(i',j)为右端点,同时向左扩展可以得到多长的相等区域
以同行不同列的两个格子(i,j)和(i,j')为最底端,同时向上扩展可以得到多长的相等区域
以格子(i,j)为中心,向左右扩展可以得到多长的对称区域
以格子(i,j)为中心,向上下扩展可以得到多长的对称区域
以两个横向相邻的格子(i,j-1)和(i,j)为中心,向左右扩展可以得到多长的对称区域
以两个纵向相邻的格子(i-1,j)和(i,j)为中心,向上下扩展可以得到多长的对称区域

    上面的每个预处理都可以在三方的时间里完成,动态规划的决策降到常数级别,因此总的复杂度还是三方。

    同样地,这也只是我出这道题的本意。我事先已经想到,这道题应该还有很多其它的算法,只是没想到会有那么多。一个比较容易想到的算法是,执行与上面相同的预处理后,枚举某个格子(或某个交叉点)为中心,向外一层一层地进行扩展。虽然这样的复杂度仍然是三方的,并且与前面的算法实质相同,但它的效率显然更高,因为你可以在无法再向外扩展时停止最里面的那个循环,继续枚举下一个中心点。
    同样是枚举正方形的中心点,只使用上面的后4个预处理也可以解决这个问题。我们可以在线性的时间内找出,以某个格子(或交叉点)为中心的最大的合法正方形。假如这个最大的正方形边长为k,这相当于我们同时找到了(k+1) div 2个正方形来。至于如何找到这个最大的正方形,还是留给大家思考吧。
    Cockhorse想到了一个非常巧妙的算法,预处理结束后它可以在常数时间内判断任一个给定的小正方形是否满足题目要求。用f[i,j,k]表示,以第i行中(i,j)及其右边相邻的k-1个格子(共k个格子)来作为底边,所能得到的左右对称的矩形其最大高度是多少。则当f[i,j+1,k-2]为True且(i,j)格与(i,j+k-1)格花色相等时f[i,j,k]=f[i-1,j,k]+1(否则f[i,j,k]=0)。同样地,再用g[i,j,k]表示以(j,i)..(j+k-1,i)作为右边界,使矩形上下对称的最大宽度。这样,判断任意一个正方形是否满足题目要求,只需要检查它的底边和右边能够产生的最大对称区域是否可以覆盖该正方形即可。
    当然,这道题目还有很多其它的算法,这里就不一一列举了。

Problem 3: 流言的传播
    给定一个图,把图中所有点构成的点集叫做U,另指定一个不等于全集的子集S,那么所有一个顶点在S里另一个顶点在U-S里的边就叫做S点集的“割边”,因为去掉所有这样的边后S集将孤立出来。这道题就是需要你找出一个边集E,使得不管S集是什么,对应的割边中权值最小的那一条一定在边集E中。这样的边集肯定是存在的,比如所有边构成的集合就是一个满足条件的边集。这道题希望你找到的边集E里所含的边尽可能的少。
    一个错误的贪心法是,去掉每个点的邻接边中权值最小的那一条。这种算法显然不对,比如下面就是一个鲜活的反例:

o—–o—–o—–o
   2     3     1

    在上图中,每个点的邻接边中权值最小的不是1就是2,这样的话中间那条边就被保留了下来,于是令S集为左边两个点(或右边两个点),割边仍然一条不少。很容易想到,要想让任意S集的割边中权值最小的被去掉,首先得保证边集E连通了所有的点,否则令S等于任一连通分量,边集E里都不会包含任何一条割边。这样,边集E至少要有n-1条边。
&n
bsp;   考虑到最优解至少是n-1条边,这n-1条边必须连通所有的点,而所选边的权值都应该很小,于是想到最后的答案很可能就是最小生成树。现在假设我们已经选择了某些边,这些边形成了若干个连通分量。考虑所有连接两个不同的连通分量的边(两顶点不在同一连通分量的边)中权值最小的一条,那这条边必须要选,否则令S集为其中一个连通分量,题目条件就不能达到了。这不就是Kruskal算法吗?
    且慢,我们还没有证明,对任意一个S集,最小生成树都符合条件。证明其实很简单,假设权值最小的割边e不在最小生成树E中,添加割边e将形成一个回路。这条回路将从S集的某个点出发,经过e跑到U-S里,最后必须还要回到S集。回到S集必然要经过另一条割边e',而显然e'>e(因为e是权值最小的割边,且题目说了没有权值相同的边),于是边集E+{e}-{e'}就成了更小的生成树。

Problem 4: 表白机器人
    第四题是整个模拟赛中最简单的题,它不需要你构造任何算法,你只需要按照题目意思进行模拟即可。和去年的NOIp一样,纯粹考编程能力的题目并没有放在第一道题的位置上。这提示大家拿到题目后要先看完所有的题,特别是看到最后一道题时千万别慌,很可能把它的衣服扒光了一看,发现它居然是一道赤裸裸的送分题。
    为了加快速度,先预处理出一个数组wall[i][j][k],表示第i行第j列往k(1<=k<=4)方向上走是否走得通。然后枚举所有可能的命令序列,模拟机器人的行走过程,看它是否能到达终点。在模拟过程中,你需要用一个数组hash[i][j][k]表示机器人曾在序列的第k个命令后到达第i行第j列的位置,并在模拟过程中不断更新hash数组;当某一次命令后机器人还没走到右上角,而对应的hash值已经为True了,则机器人的行动将从这里开始循环,永远也到不了右上角了。
    虽然这道题不需要任何剪枝,但我还想再说一句。这道题有一个非常有趣的剪枝:命令序列的第一个指令只能是U或R,最后一个指令也只能是U或R。大家想想这是为什么。

OI/MO必备:巨牛无比的公式、定理速查表

    数学考试时我最怕的就是三角函数,几十个公式我一个都背不到。那时我有了制作公式定理表的想法。经过反复尝试,我那张表已经设计得非常合理了,表里的内容也初具规模。我复印了几份给我的几个同学,反映都还不错,其它的同学听说了都想来找我要一份。当初我以为我那张表已经很牛了,今天终于发现我错了。
    刚才偶然发现一份非常牛的公式定理表,满满写了10页,不但包含有完整的三角公式、微分表、积分表,还有排列组合公式、概率公式、质数表、杨辉三角等等,甚至还有主定理、图论概念等东西。想要公式定理手册的OI/MO牛不用再去买中学数理化的口袋书了,你真正需要的东西那上面根本没有;这10页的速查表才是真正为你设计的,打印一份随身携带更能体现你nerd/geek的身份……
    点击这里下载(pdf, 154KB) 请勿直接链接到此文件

十个利用矩阵乘法解决的经典题目

    好像目前还没有这方面题目的总结。这几天连续看到四个问这类题目的人,今天在这里简单写一下。这里我们不介绍其它有关矩阵的知识,只介绍矩阵乘法和相关性质。
    不要以为数学中的矩阵也是黑色屏幕上不断变化的绿色字符。在数学中,一个矩阵说穿了就是一个二维数组。一个n行m列的矩阵可以乘以一个m行p列的矩阵,得到的结果是一个n行p列的矩阵,其中的第i行第j列位置上的数等于前一个矩阵第i行上的m个数与后一个矩阵第j列上的m个数对应相乘后所有m个乘积的和。比如,下面的算式表示一个2行2列的矩阵乘以2行3列的矩阵,其结果是一个2行3列的矩阵。其中,结果的那个4等于2*2+0*1:
    
    下面的算式则是一个1 x 3的矩阵乘以3 x 2的矩阵,得到一个1 x 2的矩阵:
    

    矩阵乘法的两个重要性质:一,矩阵乘法不满足交换律;二,矩阵乘法满足结合律。为什么矩阵乘法不满足交换律呢?废话,交换过来后两个矩阵有可能根本不能相乘。为什么它又满足结合律呢?仔细想想你会发现这也是废话。假设你有三个矩阵A、B、C,那么(AB)C和A(BC)的结果的第i行第j列上的数都等于所有A(ik)*B(kl)*C(lj)的和(枚举所有的k和l)。

经典题目1 给定n个点,m个操作,构造O(m+n)的算法输出m个操作后各点的位置。操作有平移、缩放、翻转和旋转
    这里的操作是对所有点同时进行的。其中翻转是以坐标轴为对称轴进行翻转(两种情况),旋转则以原点为中心。如果对每个点分别进行模拟,那么m个操作总共耗时O(mn)。利用矩阵乘法可以在O(m)的时间里把所有操作合并为一个矩阵,然后每个点与该矩阵相乘即可直接得出最终该点的位置,总共耗时O(m+n)。假设初始时某个点的坐标为x和y,下面5个矩阵可以分别对其进行平移、旋转、翻转和旋转操作。预先把所有m个操作所对应的矩阵全部乘起来,再乘以(x,y,1),即可一步得出最终点的位置。
    

经典题目2 给定矩阵A,请快速计算出A^n(n个A相乘)的结果,输出的每个数都mod p。
    由于矩阵乘法具有结合律,因此A^4 = A * A * A * A = (A*A) * (A*A) = A^2 * A^2。我们可以得到这样的结论:当n为偶数时,A^n = A^(n/2) * A^(n/2);当n为奇数时,A^n = A^(n/2) * A^(n/2) * A (其中n/2取整)。这就告诉我们,计算A^n也可以使用二分快速求幂的方法。例如,为了算出A^25的值,我们只需要递归地计算出A^12、A^6、A^3的值即可。根据这里的一些结果,我们可以在计算过程中不断取模,避免高精度运算。

经典题目3 POJ3233 (感谢rmq)
    题目大意:给定矩阵A,求A + A^2 + A^3 + … + A^k的结果(两个矩阵相加就是对应位置分别相加)。输出的数据mod m。k<=10^9。
    这道题两次二分,相当经典。首先我们知道,A^i可以二分求出。然后我们需要对整个题目的数据规模k进行二分。比如,当k=6时,有:
    A + A^2 + A^3 + A^4 + A^5 + A^6 =(A + A^2 + A^3) + A^3*(A + A^2 + A^3)
    应用这个式子后,规模k减小了一半。我们二分求出A^3后再递归地计算A + A^2 + A^3,即可得到原问题的答案。

经典题目4 VOJ1049
    题目大意:顺次给出m个置换,反复使用这m个置换对初始序列进行操作,问k次置换后的序列。m<=10, k<2^31。
    首先将这m个置换“合并”起来(算出这m个置换的乘积),然后接下来我们需要执行这个置换k/m次(取整,若有余数则剩下几步模拟即可)。注意任意一个置换都可以表示成矩阵的形式。例如,将1 2 3 4置换为3 1 2 4,相当于下面的矩阵乘法:
    
    置换k/m次就相当于在前面乘以k/m个这样的矩阵。我们可以二分计算出该矩阵的k/m次方,再乘以初始序列即可。做出来了别忙着高兴,得意之时就是你灭亡之日,别忘了最后可能还有几个置换需要模拟。

经典题目5 《算法艺术与信息学竞赛》207页(2.1代数方法和模型,[例题5]细菌,版次不同可能页码有偏差)
    大家自己去看看吧,书上讲得很详细。解题方法和上一题类似,都是用矩阵来表示操作,然后二分求最终状态。

经典题目6 给定n和p,求第n个Fibonacci数mod p的值,n不超过2^31
    根据前面的一些思路,现在我们需要构造一个2 x 2的矩阵,使得它乘以(a,b)得到的结果是(b,a+b)。每多乘一次这个矩阵,这两个数就会多迭代一次。那么,我们把这个2 x 2的矩阵自乘n次,再乘以(0,1)就可以得到第n个Fibonacci数了。不用多想,这个2 x 2的矩阵很容易构造出来:
    

经典题目7 VOJ1067
    我们可以用上面的方法二分求出任何一个线性递推式的第n项,其对应矩阵的构造方法为:在右上角的(n-1)*(n-1)的小矩阵中的主对角线上填1,矩阵第n行填对应的系数,其它地方都填0。例如,我们可以用下面的矩阵乘法来二分计算f(n) = 4f(n-1) – 3f(n-2) + 2f(n-4)的第k项:
    
    利用矩阵乘法求解线性递推关系的题目我能编出一卡车来。这里给出的例题是系数全为1的情况。

经典题目8 给定一个有向图,问从A点恰好走k步(允许重复经过边)到达B点的方案数mod p的值
    把给定的图转为邻接矩阵,即A(i,j)=1当且仅当存在一条边i->j。令C=A*A,那么C(i,j)=ΣA(i,k)*A(k,j),实际上就等于从点i到点j恰好经过2条边的路径数(枚举k为中转点)。类似地,C*A的第i行第j列就表示从i到j经过3条边的路径数。同理,如果要求经过k步的路径数,我们只需要二分求出A^k即可。

经典题目9 用1 x 2的多米诺骨牌填满M x N的矩形有多少种方案,M<=5,N<2^31,输出答案mod p的结果
    
    我们以M=3为例进行讲解。假设我们把这个矩形横着放在电脑屏幕上,从右往左一列一列地进行填充。其中前n-2列已经填满了,第n-1列参差不齐。现在我们要做的事情是把第n-1列也填满,将状态转

Matrix67生日邀请赛 完全题解发布

题目在这里:http://www.matrix67.com/blog/article.asp?id=241

如果机房马上要关门了,或者你急着要和MM约会,请看简要题解:

1. 用类似于传统hanoi的递归方法可以做到3^n-1次。这显然是最多的了,因为总的状态数也只有3^n个。
2. 可以证明,竞赛图中不存在环当且仅当所有顶点的出度从小到大排列依次为0, 1, 2, … , n-1 。
3. 在最短路树上做树状DP,需要多叉转二叉。注意几种需要输出0的情况。
4. 搜索,算是练基本功了。用位运算优化,不加任何剪枝就能过。

否则,请慢慢阅读——

Problem 1: 为什么最少
    如果你还不熟悉Hanoi塔的解法,去题目中提到的那篇日志看看吧。如果你已经熟悉Hanoi塔的解法,你会立刻想到这道题的解法:依然是递归地解决。至于怎么递归,样例已经告诉我们了:把前n-1个金片从1号柱搬到3号柱,把第n片移到2号柱,又把那n-1片从3号柱搬回1号柱,再把第n片搬到3号柱,最后把那n-1个金片又搬过来,完成整个操作。
    我们下面解决三个问题:为什么这样不会重复出现状态,这样的移动步数是多少,为什么这样的操作步数是最多的。
    为什么这样不会出现重复的状态呢?因为我们假设前n-1个金片的移动过程中没有重复状态,而三次对n-1的调用时整个状态由于第n个金片的位置不同而不同。
    这样的方法获得的操作步数是多少呢?答案是3^n-1。我们可以用数学归纳法证明,n=1时步数为2显然正确,而f(n+1)=3f(n)+2=3*(3^n-1)+2=3^(n+1)-1。
    为什么这样的操作步数是最多的呢?废话,这样的操作步数当然是最多的,因为总的状态数也只有3^n个(每个金片的三种可能的位置确定了一种状态),你的移动步骤能比这个数目还多就见鬼了。

    这道题有人用了math库,没有提供math库导致无法编译是我的失误,向大家道歉。

    Hanoi问题的变种太多了,比如多根柱子、单向移动、双色金片等等。dd上次不是也出了一题么。

    这题代码很短,我公布在下面。
program whyleast;

procedure solve(t,a,b:integer);
begin
   if t=0 then exit else
   begin
      solve(t-1,a,b);
      writeln(a,' ',2);
      solve(t-1,b,a);
      writeln(2,' ',b);
      solve(t-1,a,b);
   end;
end;

{====main====}
var
   n,i:integer;
   ans:longint=1;
begin
   assign(input,'whyleast.in');
   reset(input);
   assign(output,'whyleast.out');
   rewrite(output);
  
   readln(n);
   for i:=1 to n do ans:=ans*3;
   writeln(ans-1);
   solve(n,1,3);
  
   close(input);
   close(output);
end.

Problem 2: 身高控制计划
    一个竞赛图是指任两个点之间都有一条有向边的图。竞赛图有很多奇妙的性质,比如一个竞赛图必然存在一条经过所有节点的路等等。
    下面我们证明,竞赛图中不存在环当且仅当所有顶点的出度从小到大排列依次为0, 1, 2, … , n-1 :
    如果一个有向图的所有点出度都至少为1,那么这个图一定有环,因为在找到环之前DFS总可以找到新的节点。如果有向图无环,必然存在一个点没有出度。由于任两点之间都有有向边,那么其它所有点都要连一条边指向它,这样其它所有点的出度都至少为1了。删掉这个出度为0的点后剩下的图仍然无环,不断对剩下的图继续上面的过程就得到了我们的结论。
    现在我们的算法就很明确了,首先统计初始状态下的出度,然后设计某种数据结构完成两种操作:改变一个数(加一减一),询问所有数是否恰好为0, 1, 2, … , n-1 。
    统计初始状态下的出度方法有很多,这里介绍两个。首先对身高排序,然后对于每个人进行二分,寻找有序数列中该数的4/5和5/4各在什么地方。还有一种方法也是先排序,然后从左到右扫描整个序列,并保持两个指针始终指向4/5和5/4处。每次开始处理一个新的数时都把两个指针适当地右移直到超出了这个数的4/5或5/4为止。两种方法都是O(nlogn)。别以为第二种方法是线性的哦,线性算法之前还有一个排序呢。
    操作的处理也有不少方法。最简单的方法就是统计当前图中出度的数目有多少种。就是说,用a[i]表示出度为i的点有多少个,然后不断更新a[i]>0的有多少个。当这个数目等于n时我们就认为图中没有环(因为出度可能的取值只有从0到n-1共n种)。
    注意,由于同一条边可能被操作多次,因此需要一个Hash表(开散列)来判重。具体地说,你需要判断这条边以前被操作过奇数次还是偶数次,以决定哪边的出度要增加,哪边的出度要减小。

Problem 3: 狼的复仇

    把这个问题中所有的最短路都画出来是什么样子?它一定是一棵树!为什么?首先,图肯定是连通的,因为源点到任一个点都有一条最短路;其次,图肯定无环,因为源点到任一个点只有一条最短路(环的出现将意味着某些点有更短的路存在)。仔细想一下Dijkstra的算法过程,不难想到Dijkstra算法的实质就是在建这棵树——每一次由x节点加上边x-y扩展到y节点就记作x是y的父亲。注意观察上图中左图是如何变成右图的。这样,题目变成了这种形式:在有根树上,除根节点之外的其它节点中选择一些节点,使得这些节点和它们所有祖先的权值和最大。这是一个经典的树型动态规划模型。我们用f[i,j]表示以节点i为根节点的子树花费j个单位的材料最多可以得到多大的攻击力。令节点1的材料和攻击力都为0,那么最后输出f[1,0..k]中的最大值即可。决策分为两类,要么该位置建一个塔,要么把所有材料适当地分给儿子(自己就不需要再建了)。但这样的复杂度太高,我们需要用DP嵌套或者更巧妙的多叉转二叉来解决。
    DP嵌套理解起来更简单,它主要是解决这样一个子问题:若某个节点有m个儿子,我们需要寻找当j1+j2+…+jm等于某个定值时f[儿子1,j1]+f[儿子2,j2]+…+f[儿子m,jm]的最大值。这个子问题与我的某次模拟赛中论文课题选择那道DP题几乎是一模一样,看一看那道题就明白了。下面简单描述多叉转二叉的方法。

    如果你还不知道多叉转二叉的话,这道题是一个绝好的学习材料。一棵多叉树可以用“左儿子右兄弟”的方法转为二叉树,具体的说就是把多叉树转化为这种形式:节点的左儿子才是真正的儿子,节点的右儿子只是和它同辈的兄弟。注意看上图的左图是如何变成右图的。现在,我们的f[i,j]表示