重点结论:对于一个Nim游戏的局面(a1,a2,...,an),它是P-position当且仅当a1^a2^...^an=0,其中^表示位异或(xor)运算。
Nim游戏是博弈论中最经典的模型(之一?),它又有着十分简单的规则和无比优美的结论,由这个游戏开始了解博弈论恐怕是最合适不过了。
Nim游戏是组合游戏(Combinatorial Games)的一种,准确来说,属于“Impartial Combinatorial Games”(以下简称ICG)。满足以下条件的游戏是ICG(可能不太严谨):1、有两名选手;2、两名选手交替对游戏进行移动(move),每次一步,选手可以在(一般而言)有限的合法移动集合中任选一种进行移动;3、对于游戏的任何一种可能的局面,合法的移动集合只取决于这个局面本身,不取决于轮到哪名选手操作、以前的任何操作、骰子的点数或者其它什么因素; 4、如果轮到某名选手移动,且这个局面的合法的移动集合为空(也就是说此时无法进行移动),则这名选手负。根据这个定义,很多日常的游戏并非ICG。例如象棋就不满足条件3,因为红方只能移动红子,黑方只能移动黑子,合法的移动集合取决于轮到哪名选手操作。
通常的Nim游戏的定义是这样的:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。
这游戏看上去有点复杂,先从简单情况开始研究吧。如果轮到你的时候,只剩下一堆石子,那么此时的必胜策略肯定是把这堆石子全部拿完一颗也不给对手剩,然后对手就输了。如果剩下两堆不相等的石子,必胜策略是通过取多的一堆的石子将两堆石子变得相等,以后如果对手在某一堆里拿若干颗,你就可以在另一堆中拿同样多的颗数,直至胜利。如果你面对的是两堆相等的石子,那么此时你是没有任何必胜策略的,反而对手可以遵循上面的策略保证必胜。如果是三堆石子……好像已经很难分析了,看来我们必须要借助一些其它好用的(最好是程式化的)分析方法了,或者说,我们最好能够设计出一种在有必胜策略时就能找到必胜策略的算法。
定义P-position和N-position,其中P代表Previous,N代表Next。直观的说,上一次move的人有必胜策略的局面是P-position,也就是“后手可保证必胜”或者“先手必败”,现在轮到move的人有必胜策略的局面是N-position,也就是“先手可保证必胜”。更严谨的定义是:1.无法进行任何移动的局面(也就是terminal position)是P-position;2.可以移动到P-position的局面是N-position;3.所有移动都导致N-position的局面是P-position。
按照这个定义,如果局面不可能重现,或者说positions的集合可以进行拓扑排序,那么每个position或者是P-position或者是N-position,而且可以通过定义计算出来。
以Nim游戏为例来进行一下计算。比如说我刚才说当只有两堆石子且两堆石子数量相等时后手有必胜策略,也就是这是一个P-position,下面我们依靠定义证明一下(3,3)是一个P是一个P是一个P-position。首先(3,3)的子局面(也就是通过合法移动可以导致的局面)有(0,3)(1,3)(2,3)(显然交换石子堆的位置不影响其性质,所以把(x,y)和(y,x)看成同一种局面),只需要计算出这三种局面的性质就可以了。 (0,3)的子局面有(0,0)、(0,1)、(0,2),其中(0,0)显然是P-position,所以(0,3)是N-position(只要找到一个是P-position的子局面就能说明是N-position)。(1,3)的后继中(1,1)是P-position(因为(1,1)的唯一子局面(0,1)是N-position),所以(1,3)也是N-position。同样可以证明(2,3)是N-position。所以(3,3)的所有子局面都是N-position,它就是P-position。通过一点简单的数学归纳,可以严格的证明“有两堆石子时的局面是P-position当且仅当这两堆石子的数目相等”。
根据上面这个过程,可以得到一个递归的算法——对于当前的局面,递归计算它的所有子局面的性质,如果存在某个子局面是P-position,那么向这个子局面的移动就是必胜策略。当然,可能你已经敏锐地看出有大量的重叠子问题,所以可以用DP或者记忆化搜索的方法以提高效率(简单的博弈问题想到这一步就可以了)。但问题是,利用这个算法,对于某个Nim游戏的局面(a1,a2,...,an)来说,要想判断它的性质以及找出必胜策略,需要计算O(a1*a2*...*an)个局面的性质,不管怎样记忆化都无法降低这个时间复杂度。所以我们需要更高效的判断Nim游戏的局面的性质的方法。
直接说结论好了。(Bouton's Theorem)对于一个Nim游戏的局面(a1,a2,...,an),它是P-position当且仅当a1^a2^...^an=0,其中^表示异或(xor)运算。怎么样,是不是很神奇?我看到它的时候也觉得很神奇,完全没有道理的和异或运算扯上了关系。但这个定理的证明却也不复杂,基本上就是按照两种position的证明来的。
根据定义,证明一种判断position的性质的方法的正确性,只需证明三个命题: 1、这个判断将所有terminal position判为P-position;2、根据这个判断被判为N-position的局面一定可以移动到某个P-position;3、根据这个判断被判为P-position的局面无法移动到某个P-position。
第一个命题显然,terminal position只有一个,就是全0,异或仍然是0。
第二个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an!=0,一定存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。不妨设a1^a2^...^an=k,则一定存在某个ai,它的二进制表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时ai^k<ai一定成立。则我们可以将ai改变成ai'=ai^k,此时a1^a2^...^ai'^...^an=a1^a2^...^an^k=0。
第三个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。因为异或运算满足消去率,由a1^a2^...^an=a1^a2^...^ai'^...^an可以得到ai=ai'。所以将ai改变成ai'不是一个合法的移动。证毕。
根据这个定理,我们可以在O(n)的时间内判断一个Nim的局面的性质,且如果它是N-position,也可以在O(n)的时间内找到所有的必胜策略。Nim问题就这样基本上完美的解决了。
上一期的文章里我们仔细研究了Nim游戏,并且了解了找出必胜策略的方法。但如果把Nim的规则略加改变,你还能很快找出必胜策略吗?比如说:有n堆石子,每次可以从第1堆石子里取1颗、2颗或3颗,可以从第2堆石子里取奇数颗,可以从第3堆及以后石子里取任意颗……这时看上去问题复杂了很多,但相信你如果掌握了本节的内容,类似的千变万化的问题都是不成问题的。
现在我们来研究一个看上去似乎更为一般的游戏:给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移动者判负。事实上,这个游戏可以认为是所有Impartial Combinatorial Games的抽象模型。也就是说,任何一个ICG都可以通过把每个局面看成一个顶点,对每个局面和它的子局面连一条有向边来抽象成这个“有向图游戏”。下面我们就在有向无环图的顶点上定义Sprague-Garundy函数。
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于一个给定的有向无环图,定义关于图的每个顶点的Sprague-Garundy函数g如下:g(x)=mex{ g(y) | y是x的后继 }。
来看一下SG函数的性质。首先,所有的terminal position所对应的顶点,也就是没有出边的顶点,其SG值为0,因为它的后继集合是空集。然后对于一个g(x)=0的顶点x,它的所有后继y都满足g(y)!=0。对于一个g(x)!=0的顶点,必定存在一个后继y满足g(y)=0。
以上这三句话表明,顶点x所代表的postion是P-position当且仅当g(x)=0(跟P-positioin/N-position的定义的那三句话是完全对应的)。我们通过计算有向无环图的每个顶点的SG值,就可以对每种局面找到必胜策略了。但SG函数的用途远没有这样简单。如果将有向图游戏变复杂一点,比如说,有向图上并不是只有一枚棋子,而是有n枚棋子,每次可以任选一颗进行移动,这时,怎样找到必胜策略呢?
让我们再来考虑一下顶点的SG值的意义。当g(x)=k时,表明对于任意一个0<=i<k,都存在x的一个后继y满足g(y)=i。也就是说,当某枚棋子的SG值是k时,我们可以把它变成0、变成1、……、变成k-1,但绝对不能保持k不变。不知道你能不能根据这个联想到Nim游戏,Nim游戏的规则就是:每次选择一堆数量为k的石子,可以把它变成0、变成1、……、变成k-1,但绝对不能保持k不变。这表明,如果将n枚棋子所在的顶点的SG值看作n堆相应数量的石子,那么这个Nim游戏的每个必胜策略都对应于原来这n枚棋子的必胜策略!
对于n个棋子,设它们对应的顶点的SG值分别为(a1,a2,...,an),再设局面(a1,a2,...,an)时的Nim游戏的一种必胜策略是把ai变成k,那么原游戏的一种必胜策略就是把第i枚棋子移动到一个SG值为k的顶点。这听上去有点过于神奇——怎么绕了一圈又回到Nim游戏上了。
其实我们还是只要证明这种多棋子的有向图游戏的局面是P-position当且仅当所有棋子所在的位置的SG函数的异或为0。这个证明与上节的Bouton's Theorem几乎是完全相同的,只需要适当的改几个名词就行了。
刚才,我为了使问题看上去更容易一些,认为n枚棋子是在一个有向图上移动。但如果不是在一个有向图上,而是每个棋子在一个有向图上,每次可以任选一个棋子(也就是任选一个有向图)进行移动,这样也不会给结论带来任何变化。
所以我们可以定义有向图游戏的和(Sum of Graph Games):设G1、G2、……、Gn是n个有向图游戏,定义游戏G是G1、G2、……、Gn的和(Sum),游戏G的移动规则是:任选一个子游戏Gi并移动上面的棋子。Sprague-Grundy Theorem就是:g(G)=g(G1)^g(G2)^...^g(Gn)。也就是说,游戏的和的SG函数值是它的所有子游戏的SG函数值的异或。
再考虑在本文一开头的一句话:任何一个ICG都可以抽象成一个有向图游戏。所以“SG函数”和“游戏的和”的概念就不是局限于有向图游戏。我们给每个ICG的每个position定义SG值,也可以定义n个ICG的和。所以说当我们面对由n个游戏组合成的一个游戏时,只需对于每个游戏找出求它的每个局面的SG值的方法,就可以把这些SG值全部看成Nim的石子堆,然后依照找Nim的必胜策略的方法来找这个游戏的必胜策略了!
回到本文开头的问题。有n堆石子,每次可以从第1堆石子里取1颗、2颗或3颗,可以从第2堆石子里取奇数颗,可以从第3堆及以后石子里取任意颗……我们可以把它看作3个子游戏,第1个子游戏只有一堆石子,每次可以取1、2、3颗,很容易看出x颗石子的局面的SG值是x%4。第2个子游戏也是只有一堆石子,每次可以取奇数颗,经过简单的画图可以知道这个游戏有x颗石子时的SG值是x%2。第3个游戏有n-2堆石子,就是一个Nim游戏。对于原游戏的每个局面,把三个子游戏的SG值异或一下就得到了整个游戏的SG值,然后就可以根据这个SG值判断是否有必胜策略以及做出决策了。其实看作3个子游戏还是保守了些,干脆看作n个子游戏,其中第1、2个子游戏如上所述,第3个及以后的子游戏都是“1堆石子,每次取几颗都可以”,称为“任取石子游戏”,这个超简单的游戏有x颗石子的SG值显然就是x。其实,n堆石子的Nim游戏本身不就是n个“任取石子游戏”的和吗?
所以,对于我们来说,SG函数与“游戏的和”的概念不是让我们去组合、制造稀奇古怪的游戏,而是把遇到的看上去有些复杂的游戏试图分成若干个子游戏,对于每个比原游戏简化很多的子游戏找出它的SG函数,然后全部异或起来就得到了原游戏的SG函数,就可以解决原游戏了。这种“分而治之”的思想在下一节介绍的“翻硬币游戏”中将被应用得淋漓尽致。还是敬请期待。
游戏1 l 有两个游戏者:A和B。 l 有21颗石子。 l 两人轮流取走石子,每次可取1、2或3颗。 l A先取。 l 取走最后一颗石子的人获胜,即没有石子可取的人算输。 如果剩下1、2或3颗石子,那么接下来取的人就能获胜;如果剩下4颗,那么无论接下来的人怎么取,都会出现前面这种情况,所以接下来取的人一定会输;如果剩下5、6或7颗石子,那么接下来取的人只要使得剩下4颗石子,他就能获胜。0,4,8,12,……都是下一个取石子者的必败状态。现在有21颗石子,21除以4的余数是1,所以先走者有必胜的策略,他第一次只要取走1颗石子,以后每一次都保证剩下的石子是4的倍数就行了。
什么是“平等组合游戏”? l 两人游戏。 l 有一个状态集,而且通常是有限的。 l 规定哪些状态转移是允许的。 l 所有规定对于两人来说是一样的。 l 两人轮流走步。 l 有一个终止状态,到达终止状态后游戏即告终止。 l 游戏可以在有限步内终止。
P状态和N状态 就像第一个游戏一样,状态0,4,8,……是刚才走步的人的必胜状态,我们称之为P状态;而1,2,3,5,6,7,……都是下一个走步的人的必胜状态,我们称之为N状态。 我们可以从终止状态出发,推出每一个状态,指出它是P状态还是N状态。就拿第一个游戏举例: 步骤一 将所有终止状态设为P状态。 步骤二 将所有一步之内可以到达一个P状态的状态设为N状态。 步骤三 如果一个状态,不管怎么走都只能走到N状态,那么就将这个状态设为P状态。 步骤四 返回步骤二。 如果能够走到P状态,就能获胜。因为安照上面的定义,对手不管如何选择,只可能走到N状态。接下来总存在一个P状态你可以走到。这样一直走到终止状态,你获胜。当然这里所说得都是指对于最后走步的人获胜的游戏。
我们严格的来定义P状态和N状态 l 所有的终止状态都是P状态; l 对于任何的N状态,肯定存在一种方式可以一步转到一个P状态; l 对于任何的P状态,不管怎么走步,都只能转到N状态。 而对于最后走步的人失败的游戏,只要将所有终止状态改成N状态,然后开始倒推就可以了。当然,必胜状态是N状态。也就是说,如果想胜利,就希望面对N状态,转移到P状态。
现在对游戏1略微扩展一下。 有一个决策集S,S中的元素是正整数。游戏的规则大致与游戏1一样,只是现在每次可以取的石子数必须是S中的元素。如果S={1,2,3},那么就是游戏1。 大家分析一下,当S={1,3,4}的时候,哪些状态是P状态,哪些是N状态。 我们发现P状态是{0,2,7,9,14,16,……},N状态是{1,3,4,5,6,8,10,……}。 规律是如果n除以7的余数是0或2,那么状态n就是P状态,否则就是N状态。 如果游戏开始时,石子总数是100,那么这是一个P状态,也就是说后走的人有必胜策略。
游戏2 Nim游戏 有三堆石子,分别含有x1,x2和x3颗石子。两人轮流取石子,每次可以选择一堆,从这堆里取走任意多颗石子,但不能不取。取走最后一颗石子的人获胜。
我们用三元组来表示状态,很明显(0, 0, 0)是唯一的终止状态,是P状态。 先考虑只剩一堆有石子的情况(0, 0, x),很明显这是,这些状态都是N状态。 剩两堆的情况,如果两堆的石子数相等(0, x, x),那么这些都是P状态。因为下一次走步的人一定会使得两堆石子不相等,再下一次可以使得两堆的石子数回到相等的状态,包括终止状态。如果两堆的石子数不相等,那么就是N状态。 三堆都非空的情况就复杂得多。我们可以得到(1, 1, 1)、(1, 1, 2)、(1, 1, 3)和(1, 2, 2)都是N状态,因为它们可以转变成(0, 1, 1)或(0, 2, 2),它们都是P状态。(1, 2, 3)是P状态,因为不管怎么选择,下一次一定变到N状态。
“Nim和”就是两个数二进制表示的不进位加法,也就是两个整数进行xor位运算。 定义:两个数(xm…x0)2和(ym…y0)2,是(zm…z0)2,其中zi=(xi+yi) mod 2,0<=i<=m。 例如,22和51的Nim和是37:
整数关于Nim和(以后用“+”表示)满足交换律和结合律。有单位元0,因为0+x=x。任何两个相等的数之和是0,即x+x=0。有削去律,即如果x+y=x+z,那么y=z。因为,如果x+y=x+z,两边都加上x,得到x+x+y=x+x+z,即y=z。
定理1:Nim游戏的一个状态(x1, x2, x3) 是P状态,当且仅当x1+x2+x3=0。
考虑状态(13, 12, 8)。Nim和是9,不等于0,所以这是一个N状态。
那么接下来应该怎么走,才能走到一个P状态呢?你可以从第一堆中取走9颗石子。
或者你也可以从第二堆中取走7颗石子,等等。
如果石子的堆数大于3,只要堆数是有限的,上面的定理仍然成立。即如果有n堆石子,状态(x1, x2, …, xn)是P状态的充要条件是x1+x2+…+xn=0。下面就来证明。 我们用ρ表示所有Nim和为零的状态组成的集合;用п表示ρ的补集,即所有Nim和为正整数的状态组成的集合。让我们逐一检验P状态和N状态的定义。 l 所有的终止状态都在ρ中。由于终止状态只有一个(0, 0, …, 0),0+0+…+0=0。 l 所有属于п的状态,一步之内一定可以走到ρ中的状态。找出Nim和最左端为1的那一列,然后任意选择一个这一列是1的堆,从这堆中取走若干颗石子,使得Nim和为0。这总是可以做到的,因为将那一列的1变成0,而它左边的列不用修改,这个数就肯定变小了。对于其他Nim和是1的列,只要将这个数相对列的0改成1,1改成0就可以了。 l 所有属于ρ的状态,一定转变到п中的状态。任意一个P状态(x1, x2, …, xn),不妨假设从第一堆中取出若干颗石子。如果存在x1’<x1,而(x1’, x2, …, xn)也是P状态。那么x1+x2+…+xn=0=x1’+x2+…+xn,根据前面讲的削去律,x1’=x1,与假设x1’<x1矛盾。所以(x1’, x2, …, xn)一定是N状态,属于п。
通过上面的证明,你能得到从一个N状态走到P状态的方案数吗?而且这个数是奇数。
那么,对于最后走步的人失败的Nim游戏,又怎么办呢?通常情况下,这类游戏比最后走步的人获胜的游戏难得多。但Nim游戏是个例外。我们来分析一下。 P状态和N状态的定义不变,如果初始状态是N状态,先走者有必胜策略。当超过1颗石子的堆数大于1的时候,按照前面所讲的方法走。直到超过1颗石子的堆数等于1,这时将这堆石子全部取掉或剩1颗,保证非空(剩下1颗石子)的堆数为奇数。如果初始状态是N状态,按照策略,先走者不可能将“超过1颗石子的堆数等于1”的状态留给对方,因为这样的状态不可能是P状态。而且对方不可能在一步之内从“超过1颗石子的堆数大于1”的状态变到“超过1颗石子的堆数小于1”的状态。
图游戏 现在我们使用有向图来描述一个游戏,所有的状态用顶点表示,所有合法的移动用有向边表示。接下来我们会给出Sprague-Grundy函数(简称SG函数),它比起P状态和N状态,能够提供更多的信息。
定义:用(X, F)来表示有向图G。X是顶点集,F是后继函数。设x是一个顶点,F(x)是一个集合,包含于X,任意一个元素y属于F(x),表示从x出发到y有一条边。F(x)就是x的后继集合,也可看成从x出发的决策集。如果F(x)是空集,那么就表示x是终止状态。
图游戏:一个两人游戏,在一个图G(X, F)上玩,指明一个顶点x0并按照下列的规则: l A先走,从x0开始; l 两人轮流走步; l 从顶点x出发,只能走到顶点y,y属于F(x); l 遇到终止状态,即不能走步,此人输。
对于一个图,如果不管x0是哪个点,总存在一个n,使得从x0出发的任意一条路经的长度都不超过n,那么这个图就被称为是“递增有界”的。接下来主要讨论递增有界的图游戏。 拿游戏1来举例,设有n颗石子。顶点集X={0, 1, 2, …, n},F(0)是空集,F(1)={0},F(2)={0, 1},F(k)={k-3, k-2, k-1},3<=k<=n。下图是n=10的情况。
SG函数 定义: 对于一个递增有界的图G(X, F)来说,SG函数g,是定义在X上的函数,函数值是非负整数,使得
用语言来描述就是:g(x)的值等于所有x的后继的SG函数中没有出现的最小非负整数。 对于递增有界的图,SG函数是唯一的、有界的。 所有的终止状态x,因为F(x)是空集,所以g(x)=0。
给出下图的SG函数。
例1 给出游戏1的SG函数,看看有什么规律,与P状态和N状态有什么关系。 x 0 1 2 3 4 5 6 7 8 9 10 11 … g(x) 0 1 2 3 0 1 2 3 0 1 2 3 …
例2 有一堆石子,设当前剩下n颗石子,这一步至少要取走n/2取上界颗。唯一的终止状态是剩0颗石子。给出SG函数,看看有什么规律。 x 0 1 2 3 4 5 6 7 8 9 10 11 12 … g(x) 0 1 2 2 3 3 3 3 4 4 4 4 4 …
根据例1的结果,我们猜测SG函数与P状态和N状态是有关的。如果g(x)=0,那么x就是P状态,否则x就是N状态。证明是很显然的,我们只要根据两者的定义,考虑以下三点: l 如果x是终止状态,那么g(x)=0。 l 一个状态x,如果g(x)≠0,那么一定存在一个x的后继y,使得g(y)=0。 l 一个状态x,如果g(x)=0,那么所有x的后继y,都有g(y)≠0。 当然,SG函数还包含了其他的信息,这些信息在以后会用到。
附加:SG函数模板
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于一个给定的有向无环图,定义关于图的每个顶点的Sprague-Grundy函数g如下:g(x)=mex{ g(y) | y是x的后继 },这里的g(x)即sg[x]
例如:取石子问题,有1堆n个的石子,每次只能取{1,3,4}个石子,先取完石子者胜利,那么各个数的SG值为多少?
sg[0]=0,f[]={1,3,4},
x=1时,可以取走1-f{1}个石子,剩余{0}个,mex{sg[0]}={0},故sg[1]=1;
x=2时,可以取走2-f{1}个石子,剩余{1}个,mex{sg[1]}={1},故sg[2]=0;
x=3时,可以取走3-f{1,3}个石子,剩余{2,0}个,mex{sg[2],sg[0]}={0,0},故sg[3]=1;
x=4时,可以取走4-f{1,3,4}个石子,剩余{3,1,0}个,mex{sg[3],sg[1],sg[0]}={1,1,0},故sg[4]=2;
x=5时,可以取走5-f{1,3,4}个石子,剩余{4,2,1}个,mex{sg[4],sg[2],sg[1]}={2,0,1},故sg[5]=3;
以此类推.....
x 0 1 2 3 4 5 6 7 8....
sg[x] 0 1 0 1 2 3 2 0 1....
计算从1-n范围内的SG值。
f(存储可以走的步数,f[0]表示可以有多少种走法)
f[]需要从小到大排序
1.可选步数为1~m的连续整数,直接取模即可,SG(x) = x % (m+1);
2.可选步数为任意步,SG(x) = x;
3.可选步数为一系列不连续的数,用GetSG()计算
模板1:
1 //f[]:可以取走的石子个数 2 //sg[]:0~n的SG函数值 3 //hash[]:mex{} 4 int f[N],sg[N],hash[N]; 5 void getSG(int n) 6 { 7 int i,j; 8 memset(sg,0,sizeof(sg)); 9 for(i=1;i<=n;i++) 10 { 11 memset(hash,0,sizeof(hash)); 12 for(j=1;f[j]<=i;j++) 13 hash[sg[i-f[j]]]=1; 14 for(j=0;j<=n;j++) //求mes{}中未出现的最小的非负整数 15 { 16 if(hash[j]==0) 17 { 18 sg[i]=j; 19 break; 20 } 21 } 22 } 23 }
模板2:
1 //注意 S数组要按从小到大排序 SG函数要初始化为-1 对于每个集合只需初始化1遍 2 //n是集合s的大小 S[i]是定义的特殊取法规则的数组 3 int s[110],sg[10010],n; 4 int SG_dfs(int x) 5 { 6 int i; 7 if(sg[x]!=-1) 8 return sg[x]; 9 bool vis[110]; 10 memset(vis,0,sizeof(vis)); 11 for(i=0;i<n;i++) 12 { 13 if(x>=s[i]) 14 { 15 SG_dfs(x-s[i]); 16 vis[sg[x-s[i]]]=1; 17 } 18 } 19 int e; 20 for(i=0;;i++) 21 if(!vis[i]) 22 { 23 e=i; 24 break; 25 } 26 return sg[x]=e; 27 }
hdu 1848
题意:取石子问题,一共有3堆石子,每次只能取斐波那契数个石子,先取完石子者胜利,问先手胜还是后手胜
AC代码如下:
1 #include<stdio.h> 2 #include<string.h> 3 #define N 1001 4 //f[]:可以取走的石子个数 5 //sg[]:0~n的SG函数值 6 //hash[]:mex{} 7 int f[N],sg[N],hash[N]; 8 void getSG(int n) 9 { 10 int i,j; 11 memset(sg,0,sizeof(sg)); 12 for(i=1;i<=n;i++) 13 { 14 memset(hash,0,sizeof(hash)); 15 for(j=1;f[j]<=i;j++) 16 hash[sg[i-f[j]]]=1; 17 for(j=0;j<=n;j++) //求mes{}中未出现的最小的非负整数 18 { 19 if(hash[j]==0) 20 { 21 sg[i]=j; 22 break; 23 } 24 } 25 } 26 } 27 int main() 28 { 29 int i,m,n,p; 30 f[0]=f[1]=1; 31 for(i=2;i<=16;i++) 32 f[i]=f[i-1]+f[i-2]; 33 getSG(1000); 34 while(scanf("%d%d%d",&m,&n,&p)!=EOF) 35 { 36 if(m==0&&n==0&&p==0) 37 break; 38 if((sg[m]^sg[n]^sg[p])==0) 39 printf("Nacci\n"); 40 else 41 printf("Fibo\n"); 42 } 43 return 0; 44 }
hdu 1536
题意:首先输入K 表示一个集合的大小 之后输入集合 表示对于这对石子只能去这个集合中的元素的个数
之后输入 一个m 表示接下来对于这个集合要进行m次询问
之后m行 每行输入一个n 表示有n个堆 每堆有n1个石子 问这一行所表示的状态是赢还是输 如果赢输入W否则L
1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 using namespace std; 5 //注意 S数组要按从小到大排序 SG函数要初始化为-1 对于每个集合只需初始化1遍 6 //n是集合s的大小 S[i]是定义的特殊取法规则的数组 7 int s[110],sg[10010],n; 8 int SG_dfs(int x) 9 { 10 int i; 11 if(sg[x]!=-1) 12 return sg[x]; 13 bool vis[110]; 14 memset(vis,0,sizeof(vis)); 15 for(i=0;i<n;i++) 16 { 17 if(x>=s[i]) 18 { 19 SG_dfs(x-s[i]); 20 vis[sg[x-s[i]]]=1; 21 } 22 } 23 int e; 24 for(i=0;;i++) 25 if(!vis[i]) 26 { 27 e=i; 28 break; 29 } 30 return sg[x]=e; 31 } 32 int main() 33 { 34 int i,m,t,num; 35 while(scanf("%d",&n)&&n) 36 { 37 for(i=0;i<n;i++) 38 scanf("%d",&s[i]); 39 memset(sg,-1,sizeof(sg)); 40 sort(s,s+n); 41 scanf("%d",&m); 42 while(m--) 43 { 44 scanf("%d",&t); 45 int ans=0; 46 while(t--) 47 { 48 scanf("%d",&num); 49 ans^=SG_dfs(num); 50 } 51 if(ans==0) 52 printf("L"); 53 else 54 printf("W"); 55 } 56 printf("\n"); 57 } 58 return 0; 59 }
多个组合游戏的并
给定若干个组合游戏,可以按照下面的规则将它们并成一个新的游戏。 l 对每个游戏给定初始状态。 l 两人轮流走步,从A开始。 l 每一轮,选择一个未到达终止状态的游戏,在这个游戏中按照规则走一步,其他游戏的状态不变。 l 最后一个走步者获胜,即走完之后所有游戏都到达终止状态。 我们称这个新的游戏为“多个组合游戏的并”。我们要来看如何用每一个游戏的SG函数来求这个新的组合游戏的SG函数。
n个图游戏的并 定义:有n个递增有界的图游戏G1(X1, F1),……,Gn(Xn, Fn)。把它们合并成一个新的游戏G(X, F),记为G=G1+G2+…+Gn。X是所有游戏顶点集的笛卡尔积,即X=X1*X2*…*Xn。也就是说,我们用n元组(x1, x2, …, xn)来表示G中的顶点x,其中xi属于Xi,对于所有的i。x的后继F(x)可以定义成:
这样定义的新的游戏G,一定也是递增有界的。把每个游戏的界相加,就得到了新游戏的界。 正如Nim游戏那样,如果堆数是1,那么非常简单;如果堆数是2,也很容易分析;但堆数如果大于2,就不是很明显了。所以即使每个图游戏都是很平凡的,n个图游戏的并也可能相当复杂。
下面介绍的SG定理可以看成是定理1的一般化。 定理2 设G=G1+G2+…+Gn,Gi的SG函数是gi,i=1, 2, …, n。那么G的SG函数g(x1, x2, …, xn)=g1(x1)+g2(x2)+…+gn(xn),加法表示Nim和,即不进位的二进制加法。 证明: 令x(x1, x2, …, xn)是X中任意一点,b= g1(x1)+g2(x2)+…+gn(xn)。 根据SG函数的定义,我们要说明两点: (1)、对于任意的非负整数a(a<b),一定存在一个x的后继y,使得g(y)=a。 (2)、x的任意一个后继y,都有g(y)¹b。 首先来说明(1)。设d=a+b(nim和),d的二进制表示有k位,则2k-1<=d<2k。d的第k位是1而且a<b,所以a的第k位是0,b的第k位是1。因为b= g1(x1)+g2(x2)+…+gn(xn),所以至少存在一个分量的第k位是1,不妨设它就是g1(x1)。那么,就有d+g1(x1)<g1(x1),也就存在从x1到x1’的一次走步,使得g1(x1’) =d+g1(x1)。那么g1(x1’)+g2(x2)+…+gn(xn)=d+g1(x1)+g2(x2)+…+gn(xn) = d+b=a。 再说明(2)。反证法。不失一般性,假设后继的走步是从x1到x1’,又有g1(x1’)+g2(x2)+…+gn(xn) =g1(x1)+g2(x2)+…+gn(xn)。根据消去率,g1(x1’)=g1(x1),这与SG函数的定义不符,假设不成立。
例3、你每次可以从一堆石子中取走{1, 2, …, m}颗。对于1堆的问题,SG函数gm(x)=x mod (m+1)。如果考虑3个这样的游戏的并,第一个游戏m=3,有9颗石子;第二个游戏m=5,有10颗石子;第三个游戏m=7,有14颗石子。g(9,10,14)=g3(9)+g5(10)+g7(14)=1+4+6=3,是一个N状态。要取胜的话,下一次可以选择第三个游戏,取走1颗石子,使得g7(13)=5。