博弈论之Nim
博弈论(一):Nim游戏
重点结论:对于一个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问题就这样基本上完美的解决了。
博弈论(二):Sprague-Grundy函数
上一期的文章里我们仔细研究了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函数,就可以解决原游戏了。这种“分而治之”的思想在下一节介绍的“翻硬币游戏”中将被应用得淋漓尽致。还是敬请期待。
Game theory初步
游戏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函数还包含了其他的信息,这些信息在以后会用到。
多个组合游戏的并
给定若干个组合游戏,可以按照下面的规则将它们并成一个新的游戏。
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。那么,还有别的取法吗?
var
n,m,p,t,i:longint;
begin
readln(n);
t:=0;
for i:=1 to n do begin
readln(m,p);
t:=t xor (p mod (m+1));
end;
if t=0 then writeln('P') else writeln('N');
end.
取走-分割游戏
这种游戏允许取走某些东西,然后将原来的一个游戏分成若干个相同的游戏。
例1、Lasker’s Nim游戏:每一轮允许两种操作之一。(1)从一堆石子中取走任意多个(2)将一堆数量不少于2的石子分成都不为空的两堆。
分析:
很明显,g(0)=0,g(1)=1。状态2的后继有0,1和(1,1),它们的SG函数值分别是0,1和0,所以g(2)=2。状态3的后继有0,1,2和(1,2),它们的SG函数值分别是0,1,2和3,所以g(3)=4。状态4的后继有0,1,2,3,(1,3)和(2,2),它们的SG函数值分别是0,1,2,4,5和0,所以g(4)=3。在推一些,我们得到:
我们推测:对于所有的k>=0,有g(4k+1)=4k+1;g(4k+2)=4k+2;g(4k+3)=4k+4;g(4k+4)=4k+3。
请自行证明。
假设游戏初始时有3堆,分别有2、5和7颗石子。三堆的SG函数值分别是2、5和8,它们的Nim和等于15。所以要走到P状态,就要使得第三堆的SG值变成7,可以将第三堆分成按1和6分成两堆。
var
g:array[0..100] of longint;
b:array[0..100] of boolean;
n,i,j:longint;
begin
readln(n);
g[0]:=0;
for i:=1 to n do begin
fillchar(b,sizeof(b),false);
for j:=0 to i-1 do b[g[j]]:=true;
for j:=1 to i div 2 do b[g[j] xor g[i-j]]:=true;
j:=0;
while b[j] do inc(j);
g[i]:=j;
writeln(i,': ',j);
end;
end.
PAS
2人玩的游戏,一个p*1的棋盘和红、绿、蓝三种棋子,棋子的大小分别是c*1、z*1和n*1,每种颜色的棋子个数无限。两人轮流摆放棋子,规则是:
棋子不得超出棋盘范围;
棋子不能有任何部分重叠;
如果哪个人没有棋子可放,即算输。
判断先手是否有必胜策略。
输入:
第一行是正整数c、z和n,都不超过1000。
第二行是m,表示棋盘种类。接下来的m行,每行一个正整数p。m和p都不超过1000。
输出:
对于每种棋盘输出一行,如果先手必胜输出1,否则输出2。
样例
输入
1 5 1
3
1
5
6
输出
1
1
2
var
c:array[1..3] of longint;
g,q:array[0..1000] of longint;
b:array[0..1000] of boolean;
m,maxq,i,j,k:longint;
begin
assign(input,'pas.in'); reset(input);
assign(output,'pas.out'); rewrite(output);
readln(c[1],c[2],c[3]);
readln(m);
maxq:=0;
for i:=1 to m do begin
read(q[i]);
if q[i]>maxq then maxq:=q[i];
end;
g[0]:=0;
for i:=1 to maxq do begin
fillchar(b,sizeof(b),false);
for j:=1 to 3 do
for k:=0 to i-c[j] do b[g[k] xor g[i-c[j]-k]]:=true;
j:=0;
while b[j] do inc(j);
g[i]:=j;
end;
for i:=1 to m do
if g[q[i]]=0 then writeln(2) else writeln(1);
close(input); close(output);
end.
寻找必败态——一类博弈问题的快速解法
博弈是信息学和数学试题中常会出现的一种类型,算法灵活多变是其最大特点,而其中有一类试题更是完全无法用常见的博弈树来进行解答。 寻找必败态即为针对此类试题给出一种解题思路。
此类问题一般有如下特点:
1、博弈模型为两人轮流决策的非合作博弈。即两人轮流进行决策,并且两人都使用最优策略来获取胜利。
2、博弈是有限的。即无论两人怎样决策,都会在有限步后决出胜负。
3、公平博弈。即两人进行决策所遵循的规则相同。
以下题目都属于这一类:
POJ1740 A New Stone Game
MIPT100 Nim Game -- who is the winner?
POJ1704 Georgia and Bob
POJ1067 取石子游戏
本着先理论后实践的原则,本文先对“寻找必败态”做出理论上的解释:
要理解这种思想,首先要明白什么叫必败态。说简单点,必败态就是“在对方使用最优策略时,无论做出什么决策都会导致失败的局面”。其他的局面称为胜态,值得注意的是在胜态下做出错误的决策也有可能导致失败。此类博弈问题的精髓就是让对手永远面对必败态。
必败态和胜态有着如下性质:
1、若面临末状态者为获胜则末状态为胜态否则末状态为必败态。
2、一个局面是胜态的充要条件是该局面进行某种决策后会成为必败态。
3、一个局面是必败态的充要条件是该局面无论进行何种决策均会成为胜态
这三条性质正是博弈树的原理,但博弈树是通过计算每一个局面是胜态还是必败态来解题,这样在局面数很多的情况下是很难做到的,此时,我们可以利用人脑的推演归纳能力找到必败态的共性,就可以比较好的解决此类问题了。
下面就通过实际题目来做一些分析:
例1 POJ1740 A New Stone Game
题目大意是:有N堆石子,两人轮流进行操作,每一次为“操作者指定一堆石子,先从中扔掉一部分(至少一颗,可以全部扔掉),然后可以将该堆剩下的石子中的任意多颗任意移到其他未取完的堆中”,操作者无法完成操作时为负。
分析:
只有一堆时先手必胜。
有两堆时若两堆相等则后手只用和先手一样决策即可保证胜利,后手必胜。若不同则先手可以使其变成相等的两堆,先手必胜。
有三堆时先手只用一次决策即可将其变成两堆相等的局面,先手必胜。
有四堆时由于三堆必胜,无论先手后手都想逼对方取完其中一堆,而只有在四堆都为一颗时才会有人取完其中一堆,联系前面的结论可以发现,只有当四堆可以分成两两相等的两对时先手才会失败。
分析到这里,题目好像已经有了一些眉目了,凭借归纳猜想,我们猜测必败态的条件为“堆数为偶数(不妨设为2N),并且可以分为两两相等的N对”。
下面只需证明一下这个猜想。其实证明这样的猜想很简单,只用检验是否满足必败态的三条性质即可。
首先,末状态为必败态,第一条性质符合。
其次,可以证明任何一个胜态都有策略变成必败态(分奇数堆和偶数堆两种情况讨论)。
最后,证明任何一个必败态都无法变成另一个必败态(比较简单)。
由于篇幅关系,这里就不具体证明了,如果有兴趣可以自己试试∶P
接下来的程序就相当简单了,只用判断一下即可。
有些题则比这一题的条件隐蔽许多,例如:
例2 MIPT100 Nim Game -- who is the winner?
题目大意是:有N堆石子,两人轮流取,每次可以从任意一堆中取任意多颗(但至少一颗),谁先取完谁胜。
分析:
还是用按照“手推小数据=〉猜想=〉证明”的模式。
一堆时先手必胜。
两堆时若两堆相等则先手必败,否则先手胜。
三堆的情况就有点复杂了,此时,我们只好借助博弈树来在小范围内求解,从这些解中我们可以看出,对于由两个不同数字构成的两元组,都有且仅有一个三元必败态包含它,这又意味着什么呢?我们定义一个函数F(a,b),表示“包含a,b的三元必败态中的第三数”,则有
F(1,2)=3,F(1,3)=2,F(1,4)=5,F(1,5)=4,F(1,6)=7,F(1,7)=6...
F(2,1)=3,F(2,3)=1,F(2,4)=6,F(2,5)=7,F(2,6)=4,F(2,7)=5...
F(3,1)=2,F(3,2)=1,F(3,4)=7,F(3,5)=6,F(3,6)=5,F(3,7)=4...
..................................................................................
..................................................................................
敏锐的选手马上会发现,这个F(a,b)不就是a XOR(异或) b的结果么?做到这里,答案就在眼前了,‘XOR‘运算恐怕就是本题的关键。
继续求出一些四元必败态,这个性质仍然符合,于是我们猜想,必败态即为“所有堆的石子数XOR运算后结果为零的局面”。这也解释了为什么一堆石子必胜,两堆石子仅在相等时必败。
接下来又是证明:
依旧判断是否符合三条性质。
第一条第三条显然满足,关键就是第二条。
必胜态下,设所有堆石子XOR后结果为N,将其写成二进制,则至少有一堆石子写成二进制后在N的最高位上为一,则可以证明从这堆石子中取可以变成必败态,这里还是留给有兴趣的选手:)
这一题就明显没有上一题轻松了,而且这是个经典问题,结论可以记下来。下面这个例子就是例2的强化版:
例3 POJ1704 Georgia and Bob
题目大意是:一个1*M的棋盘上有N个棋子,初始位置一定,两人轮流操作,每次移动一枚棋子,要求只能向左移且至少移动一格,而且不能到达或经过以前有棋子的格子,谁无法移动棋子就算输。
分析:
乍一看这一题棋子移动还要受其他棋子的限制,好像无法求出通解,但仔细分析会发现别有洞天。
一个棋子每一次向左移的最大步数是固定的,而且随着移动减少,不是和取石子很像么?那么和取石子的区别在哪呢?就在于每一次移动时都会让右边相邻的那颗棋子移动空间变大,这样就和取石子只减不增有所不同了,我们应该怎样解决这个问题呢?
我们并不放弃将其与我们熟悉的取石子对应,但我们将策略做小小的变动:
将棋子从右端向左端每相邻两个分为一对,如果只剩一个就将棋盘左端加一格放一颗棋子与之配对,这样配对后好像和以前没有什么区别,但决策时就方便多了,因为我们大可不必关心组与组之间的距离,当对手移动一组中靠左边的棋子时,我们只需将靠右的那一颗移动相同步数即可!同时我们把每一组两颗棋子的距离视作一堆石子,在对手移动两颗棋子中靠右的那一颗时,我们就和他玩取石子游戏,这样就把本题与取石子对应上了。
本例说明有许多模型看似复杂,但经过一些巧妙的变换,便可以转化成一些我们熟悉的模型,同时也充分体现了博弈的灵活。
如果说前面的例子是介绍这种思想的运用的话,下面的方法就是讲这种思路的优越性了,因为这一题并不是走的“手推小数据=〉猜想=〉证明”的老路,而是直接利用性质推导必败条件。
例4
url=http://acm.pku.edu.cn/JudgeOnline/showmessage?message_id=4163]POJ1067
取石子游戏[/URL]
题目大意是:......题目本来就是中文的-_-b。
分析:
刚拿到这一题时,我不加思索的猜想必败态为“两堆石子的数目是2:1”,用性质判定:第一条显然符合,第二条分情况讨论每一种“胜态”都有一种固定的方法变成“必败态”,再看第三条,设第一堆有N颗,第二堆有2*N颗,则无论怎样拿都无法让第二堆保持为第一堆的两倍。证毕。
本以为此题就这么简简单单完了,但是我突然发现,当第一堆2颗,第二堆4颗时,从第二堆中取出3颗石子的话第二堆的确无法保持为第一堆的两倍,但第一堆会变成第二堆的两倍,基于此,整个猜想被彻底推翻。
于是我反转思路,干脆从性质入手。
我们令必败二元组为(a,b)形式,并令a<b。
根据性质三,有这样两个推论:
推论一:对于任意两个的必败二元组(a1,b1),(a2,b2),有a1<>a2,b1<>b2,a1<>b2,a2<>b1。
推论二:对于任意两个的必败二元组(a1,b1),(a2,b2),有b1-a1<>b2-a2。
利用性质和该推论,我们证明如下结论:“将必败二元组按首元为关键字排序,每个必败二元组中首元为未在前面的必败二元组中出现的最小正整数,并且第N组中两个数差为N”。
利用数学归纳法证明:
第一组为(1,2),满足题意。
若前N组满足题意,则有:
设为在前N组中出现的最小正整数为M,则对于二元组(M,M+N+1)有:
如果从数量为M的堆中取了石子,不妨设变成了(K,L),则L-K>N,这样就有一个包含K,且不与前面N组任何一组相同的二元组,根据推论一,这个二元组一定不是必败二元组。
如果只从数量为M+N+1的堆中取,不妨设剩下K颗,又分三种情况:
K>M,则N+1>K-M>0,根据推论二,这个二元组一定不是必败二元组。
K=M或0,显然不是必败二元组。
0<K<M,则(K,M)为包含K,且不与前面N组任何一组相同的二元组,根据推论一,这个二元组一定不是必败二元组。
综上,根据性质三,(M,M+N+1)为必败二元组,又根据排序的法则,(M,M+N+1)一定是数列的第(N+1)项。证毕。
这样利用性质和性质得出的推论,此题的必败态也完美的找出了。
从上面的例子可以看出,利用寻找必败态的思路解题对猜想和数学证明的能力要求很高,对思维的训练有很大好处,同时编程复杂度相当低,也不失为一种好的解题方法。
博弈论之Nim的更多相关文章
- 博弈论之Nim游戏
Nim游戏是组合游戏(Combinatorial Games)的一种,属于“Impartial Combinatorial Games”(以下简称ICG). 通常的Nim游戏的定义是这样的:有若干堆石 ...
- 博弈论入门——Nim游戏引入
说实话,我真的对这个游戏看得是一脸懵逼,因为(我太弱了)我没有明白一些变量的意思,所以一直很懵,现在才明白,这让我明白博弈论(还可以骗钱)博大精深; 以下是我自己思考的过程,也许不严谨,但是最终明白了 ...
- 博弈论(nim游戏,SG函数)
说到自己,就是个笑话.思考问题从不清晰,sg函数的问题证明方法就在眼前可却要弃掉.不过自己理解的也并不透彻,做题也不太行.耳边时不时会想起alf的:"行不行!" 基本的小概念 这里 ...
- HDU.2516.取石子游戏(博弈论 Fibonacci Nim)
题目链接 \(Description\) 1堆石子有n个.两人轮流取.先取者第1次可以取任意多个,但不能全部取完.以后每次取的石子数不能超过上次取子数的2倍,取完者胜.问谁能赢. \(Solution ...
- 浅谈博弈论之Nim初步(xor正确性的浅显证明)
引入 在许多地方曾流行过这样一个小游戏:摆出三堆硬币,分别包含3枚,5枚,7枚.两人轮流行动,每次可任选一堆,从中取走任意多枚硬币,可把一堆取光,但不能不取,取走最后一枚硬币者获胜. 概念 \(先手: ...
- 【博弈论】Nim游戏
百度百科 Definition 这样的游戏被称为Nim游戏: 1.有两个玩家,轮流进行操作 2.是公平游戏.即面对同一局面两个玩家所能进行的操作是相同的.例如中国象棋不是公平游戏.因为面对同一个局面, ...
- 博弈论-一堆nim博弈合在一起
今天A了张子苏大神的的题,感觉神清气爽. 一篇对于多层nim博弈讲的很透彻的博文:http://acm.hdu.edu.cn/forum/read.php?fid=9&tid=10617 我来 ...
- [SDOI2019]移动金币(博弈论+阶梯Nim+按位DP)
首先可以把问题转化一下:m堆石子,一共石子数不超过(n-m)颗,每次可以将一堆中一些石子推向前一堆,无法操作则失败,问有多少种方法使得先手必胜? 然后这个显然是个阶梯Nim,然后有这样的结论:奇数层异 ...
- 博弈论Nim取子问题,困扰千年的问题一行代码解决
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是算法与数据结构专题26篇文章,我们来看看一个新的博弈论模型--Nim取子问题. 这个博弈问题非常古老,延续长度千年之久,一直到20世纪 ...
随机推荐
- 将时间显示为“刚刚”“n分钟/小时前”等
在很多场合为了显示出信息的及时性,一般会将时间显示成“刚刚”,“5分钟前”,“3小时前”等,而不是直接将时间打印出来.比如微博,SNS类应用就最长用到这个功能.而一般存储在数据库中的时间格式为 Uni ...
- datetime.timedelta
from django.utils import timezoneimport datetime timezone.now()datetime.datetime(2014, 7, 18, 9, 42, ...
- 1.unix网络编程基础知识
接触网络编程一年多了,最近在系统的学习vnp两本书,对基础知识做一些总结,希望理解的更透彻清晰,希望能有更多的沉淀. 1.套接口地址 针对IPv4和IPv6地址族,分别定义了两种类型的套接口地址:so ...
- 一步一步重写 CodeIgniter 框架 (9) —— 使用 CodeIgniter 类库
通过前面几节的内容,我们从零开始搭建了一个非常方便的MVC框架,理解了 CodeIgniter 框架最核心的部分.然而一个框架的便利不仅仅在于提供一个MVC就可以了,它还必须具有较高的扩展性.下面将从 ...
- RelativeLayout的属性详解
1. android:layout_below="@+id/first" //在某元素的的下方: android:layout_alignBottom="@+id/fir ...
- ArrayList集合-[习题]--C#
:向集合中添加10个元素,计算平均值,求最大.最小值. ; list.AddRange(, , , , , , , , }); int Max, Min; Max = Min = (]; ; i &l ...
- abstract 关键字-- 抽象
代码: using System; namespace Console_Test { class Program { public abstract class MyClas { /// <su ...
- C++部分术语(Terms)
翻译自msdn,如有不妥当的地方,欢迎指正. 声明(Declaration):声明引入了一个名字以及其类型进入程序中,并没有定义一个相关的对象或者函数.然而,很多声明都作为定义使用. 定义(def ...
- 使用gettimeofday测试函数运行的时间
#include <time.h> #include <stdio.h> #include<sys/time.h> #define NEW_TIME_VALE st ...
- IOS 调用系统相册或照相机tab按钮显示中文