² Chomp! 博弈(巧克力游戏)
² 情人节的时候,潘典安买了一块长方形的棋盘巧克力,和他最爱的女友一起吃,巧克力由n*m块格子组成。为了增加情人节的情趣,潘典安和她女友轮流选择一个格子,并把这个格子右面,上面和右上方的巧克力全部取走。取到左下角格子的玩家输,就要给对方一个热烈的吻。假设每次都是潘典安先手,他是否存在必胜策略呢?
² 下图可以看作是一个3*8的巧克力被拿掉(6,2)和(3,2)两块后剩下的形状:
² 答案是除了1*1的棋盘,对于其他大小的棋盘,先手总能赢。有一个很巧妙的证明可以保证先手存在必胜策略,可惜这个证明不是构造性的,也就是说没有给出先手怎么下才能赢。证明如下:
² 如果后手能赢,也就是说后手有必胜策略,使得无论先手第一次取哪个石子,后手都能获得最后的胜利。那么现在假设先手取最右上角的石子(n,m),接下来后手通过某种取法使得自己进入必胜的局面。但事实上,先手在第一次取的时候就可以和后手这次取的一样,进入必胜局面了,与假设矛盾。
² Fibonacci’s Game (斐波那契博弈)
² 斐波那契博弈模型,是ACM题中常见的组合游戏中的一种,大致上是这样的:
² 有一堆个数为 n 的石子,游戏双方轮流取石子,满足:
² 1. 先手不能在第一次把所有的石子取完;
² 2. 之后每次可以取的石子数介于 1 到对手刚取的石子数的 2 倍之间(包含 1 和对手刚取的石子数的 2 倍)。
² 约定取走最后一个石子的人为赢家,求必败态。
² 这个和之前的威佐夫博弈和取石子游戏有一个很大的不同点,就是游戏规则的动态化。之前的规则中,每次可以取的石子的策略集合是基本固定的,但是这次有规则2:一方每次可以取的石子数依赖于对手刚才取的石子数。
² 这个游戏叫做Fibonacci Nim,肯定和Fibonacci数列f[n]:1,2,3,5,8,13,21,34,55,89,… 有密切的关系。如果试验一番之后,可以猜测:先手胜当且仅当n不是Fibonacci数。换句话说,必败态构成Fibonacci数列。
² 下面简单谈谈“先手败当且仅当n为Fibonacci数列”这一结论是怎么得来的。
² 这里要用到一个很有用的定理:任何正整数可以表示为若干个不连续的 Fibonacci 数之和。
² 这里定理涉及到数论,这里不做证明,想要知道证明过程的请找你们数学老师。下面只谈如何把一个正整数表示为若干个不连续的 Fibonacci 数之和。
² 比如,我们要分解83,注意到83被夹在55和89之间,于是把83可以写成83=55+28;然后再想办法分解28,28被夹在21和34之间,于是28=21+7;依此类推 7=5+2,故83=55+21+5+2。
² 如果 n 是 Fibonacci 数,比如 n = 89。89前面的两个Fibonacci 数是34和55。如果先手第一次取的石子不小于 34 颗,那么一定后手赢,因为 89 – 34 = 55 = 34 + 21 < 2*34,注意55是Fibonacci数。此时后手只要将剩下的全部取光即可,此时先手必败。故只需要考虑先手第一次取得石子数 < 34 即可,于是剩下的石子数 x 介于 55 到 89 之间,它一定不是一个 Fibonacci 数。于是我们把 x 分解成 Fibonacci 数:x = 55 + f[i] + … + f[j],其中55 > f[i] > … > f[j],如果 f[j] ≤ 先手一开始所取石子数 y 的两倍,那么对后手就是面临 x 局面的先手,所以根据之前的分析,后手只要先取 f[j] 个即可,以后再按之前的分析就可保证必胜。
² 下证:f[j] ≤ 2y
² 反证法:假设f[j]>2y,则 y < f[j]/2 = (f[j-1] + f[j-2])/2 < f[j-1]。而最初的石子数是个斐波那契数,即 n = f[k] = x + y < f[k-1] + f[i] + … + f[j] + f[j-1] ≤ f[k-1]+f[i]+f[i-1] ≤ f[k-1]+f[k-2] ≤ f[k] (注意第一个不等号是严格的),矛盾!f[j] ≤ 2y得证。
² 如果 n 不是 Fibonacci 数,比如n=83,我们看看这个分解有什么指导意义:假如先手取2颗,那么后手无法取5颗或更多,而5是一个Fibonacci数,如果猜测正确的话,(面临这5颗的先手实际上是整个游戏的后手)那么一定是整个游戏的先手取走这5颗石子中的最后一颗,而这个我们可以通过第二类归纳法来绕过,同样的道理,根据“先手败当且仅当n为Fibonacci数列”,接下去先手取走接下来的后21颗中的最后一颗,再取走后55颗中的最后一颗,那么先手赢。
² 尼姆博弈(Nimm’s Game)
² 尼姆博弈模型,是ACM题中常见的组合游戏中的一种,大致上是这样的:
² 有3堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取1个,多者不限,最后取光者得胜。
² 这种情况最有意思,它与二进制有密切关系,我们用(a,b,c)表示某种局势,首先(0,0,0)显然是必败态,无论谁面对(0,0,0) ,都必然失败;第二种必败态是(0,n,n),自己在某一堆拿走k(k ≤ n)个物品,不论k为多少,对方只要在另一堆拿走k个物品,最后自己都将面临(0,0,0)的局势,必败。仔细分析一下,(1,2,3)也是必败态,无论自己如何拿,接下来对手都可以把局势变为(0,n,n)的情形。
² 计算机算法里面有一种叫做按位模2加,叫做异或的运算,我们用符号XOR表示这种运算,这种运算和一般加法不同的一点是1 XOR 1 = 0。先看(1,2,3)的按位模2加的结果:
² 1 = 二进制01
² 2 = 二进制10
² 3 = 二进制11 XOR
² ———————
² 0 = 二进制00 (注意不进位)
²
² 对于奇异局势(0,n,n)也一样,结果也是0。
² 任何奇异局势(a,b,c)都有a XOR b XOR c = 0。
² 如果我们面对的是一个非必败态(a,b,c),要如何变为必败态呢?假设 a < b < c,我们只要将 c 变为a XOR b,即可。因为有如下的运算结果:
² a XOR b XOR (a XOR b)=(a XOR a) XOR (b XOR b) = 0 XOR 0 = 0。
² 要将c 变为a XOR b,只要对 c进行 c-(a XOR b)这样的运算即可。
² 尼姆博弈模型可以推广到:有n堆若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
² 这个游戏中的变量是堆数k和各堆的物品数N1,N2,……,Nk。对应的组合问题是,确定先手获胜还是后手获胜以及两个游戏人应该如何取物品才能保证自己获胜(获胜策略)。
² 为了进一步理解Nim取物品游戏,我们考查某些特殊情况。如果游戏开始时只有一堆物品,先手则通过取走所有的物品而获胜。现在设有2堆物品,且物品数量分别为N1和N2。游戏者取得胜利并不在于N1和N2的值具体是多少,而是取决于它们是否相等。设N1!=N2,先手从大堆中取走的物品使得两堆物品数量相等,后手再拿,于是,先手以后每次取子的数量与后手相等而最终获胜。但是如果N1= N2,则:后手只要按着先手拿的数量在另一堆中取相等数量的物品,最终获胜者将会是后手。这样,两堆的取子获胜策略就已经找到了。
² 现在我们如何从两堆的取子策略扩展到任意堆数中呢?
² 首先来回忆一下,每个正整数都有对应的一个二进制数,例如:57(10) = 111001(2) ,即:57(10)=25+24+23+20。于是,我们可以认为每一堆物品数由2的幂数的子堆组成。这样,含有57枚物品大堆就能看成是分别由数量为25、24、23、20的各个子堆组成。
² 现在考虑各大堆大小分别为N1,N2,……Nk的一般的Nim博弈。将每一个数Ni表示为其二进制数(数的位数相等,不等时在前面补0):
² N1 = as…a1a0
² N2 = bs…b1b0
² ……
² Nk = ms…m1m0
² 如果每一种大小的子堆的个数都是偶数,我们就称Nim博弈是平衡的,而对应位相加是偶数的称为平衡位,否则称为非平衡位。因此,Nim博弈是平衡的,当且仅当:
² as +bs + … + ms 是偶数,即as XOR bs XOR … XOR ms = 0
² ……
² a1 +b1 + … + m1 是偶数,即a1 XOR b1 XOR … XOR m1 = 0
² a0 +b0 + … + m0是偶数,即a0 XOR b0 XOR … XOR m0 = 0
² 于是,我们就能得出尼姆博弈中先手获胜策略:
² Bouton定理:先手能够在非平衡尼姆博弈中取胜,而后手能够在平衡的尼姆博弈中取胜。即,状态(x1, x2, x3, …, xn)为P状态当且仅当x1 xor x2 xor x3 xor … xor xn=0。这样的操作也称为Nim和(Nim Sum) 。
² 我们以一个两堆物品的尼姆博弈作为试验。设游戏开始时游戏处于非平衡状态。这样,先手就能通过一种取子方式使得他取子后留给后手的是一个平衡状态下的游戏,接着无论后手如何取子,再留给先手的一定是一个非平衡状态游戏,如此反复进行,当后手在最后一次平衡状态下取子后,先手便能一次性取走所有的物品而获胜。而如果游戏开始时游戏牌平衡状态,那根据上述方式取子,最终后手能获胜。
² 下面应用此获胜策略来考虑4堆的Nim博弈。其中各堆的大小分别为7,9,12,15枚硬币。用二进制表示各数分别为:0111,1001,1100和1111。于是可得到如下一表:
²
² 由Nim博弈的平衡条件可知,此游戏是一个非平衡状态的Nim博弈,因此,先手在按获胜策略一定能够取得最终的胜利。具体做法有多种,先手可以从大小为12的堆中取走11枚硬币,使得游戏达到平衡(如下表),
² 之后,无论后手如何取子,先手在取子后仍使得游戏达到平衡。
² 同样的道理,先手也可以选择大小为9的堆并取走5枚硬币而剩下4枚,或者,先手从大小为15的堆中取走13枚而留下2枚。
² 归根结底, Nim博弈的关键在于游戏开始时游戏处于何种状态(平衡或非平衡)和先手是否能够按照取子游戏的获胜策略来进行游戏。
² 当堆数大于2时,我们看出Bouton定理依旧适用,但还没给出严格的证明,下面用数学归纳法证明。
² 证明:如果每堆都为0,显然是P状态(必败)。下面验证P状态和N状态的后两个递推关系:
² 一、每个N状态都可以一步到达P状态。
² 证明是构造性的。检查Nim和X的二进制表示中最左边一个1,则随便挑一个该位为1的物品堆Y,根据Nim和进行调整(0变1,1变0)即可。例如Nim和为100101011,而其中有一堆为101110001。为了让Nim和变为0,只需要让操作的物品数取操作前的物品数和Nim的异或即可。
² 显然操作后物品数变小,因此和合法的。设操作前其他堆的Nim和为Z,则有Y xor Z = X。操作后的Nim和为X xor Y xor Z = X xor X = 0,是一个P状态。
² 二、每个P状态(必胜态)都不可以一步到达P状态
² 由于只能改变一堆的物品,不管修改它的哪一位,Nim的对应位一定不为0,不可能是P状态。
² 这样就证明了Bouton定理。
² Nim博弈中如果规定最后取光者输,情况是怎样的?初看起来问题要复杂很多(因为不能主动拿了,而要“躲着”拿,以免拿到最后一个物品),事实上确实有很多游戏规则比普通规则要困难很多,但对于Nim游戏来说,几乎是一样的:
² 首先按照普通规则一样的策略进行,直到恰好有一个物品数大于1的堆x。在这样的情况下,只需要把堆x中的物品拿得只剩1个物品或者拿完,让对手面临奇数堆物品,这奇数堆物品每堆恰好1个物品。这样的状态显然是必败的。由于你每次操作后需要保证Nim和为0,因此不可能在你操作后首次出现“恰好有一个物品数大于1的堆”。新游戏得到了完美解决。
² SG函数与SG定理
² 现在我们来研究一个看上去似乎更为一般的游戏:给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移动者判负。事实上,这个游戏可以认为是所有Impartial Combinatorial Games的抽象模型。也就是说,任何一个ICG都可以通过把每个局面看成一个顶点,对每个局面和它的子局面连一条有向边来抽象成这个“有向图游戏”。下面我们就在有向无环图的顶点上定义Sprague-Garundy函数,简称SG函数。
² 首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如集合N={0,1,2,3,4,5,6},则mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
² Sprague-Grundy 函数是定义在组合游戏状态上的函数,用g (X)表示X状态的g函数值。它的定义如下:
² g(x)=mex{ g(y) | y是x的后继 }。
² 或者表示为:
² g (X)= min{n| n∈N, n>=0,n≠ for y, y是x的后继}
² 形象的说就是X的g函数值为X的后继点的SG值中没有出现过的最小值。
² SG函数的性质:
² 1、所有的终点(先手必败态),也就是没有出边的顶点,其SG值为0,因为它的后继集合是空集;
² 2、对于一个g(x)=0的顶点x,它的所有后继y都满足g(y)!=0;
² 3、对于一个g(x)!=0的顶点,必定存在一个后继y满足g(y)=0。
² 下面将引入Sprague-Grundy定理,简称SG定理,并给上述的SG函数的性质进行证明。
² 通过下面的定理可以建立SG函数与N局面和P局面的关系:
² 引理:对于任意的局面x,若g(x)=0则x是P局面(必败态),否则x是N局面(必胜态)。
² 证明:我们对局面作数学归纳法:
² 1. 对于最终局面x,由定义x是P局面(必败态),而此时g(x)=0, 引理成立。
² 2. 假设局面x的所有后继局面都满足引理。
³ 若x的后继局面中存在P局面(必败态)y,则x是N局面(必胜态),同时g(y)=0,故g(x)不等于0;
³ 若x的后继局面y中不存在P局面,则显然x是P局面;同时由于不存在g(y)=0,故g(x)=0。
² 由归纳假设,引理对于所有局面x成立。
² 我们通过计算有向无环图的每个顶点的SG值,就可以对每种局面找到必胜策略了。
² 实际中,SG函数的用途远没有这样简单。如果将有向图游戏变复杂一点,比如说,有向图上并不是只有一枚棋子,而是有n枚棋子,每次可以任选一颗进行移动,这时,怎样找到必胜策略呢?
² 再来考虑一下顶点的SG值的意义。当g(x)=k时,表明对于任意一个0 ≤ i
² 也就是说:游戏者可以通过一步走棋把图的当前状态值任意的减小(当然必须保证状态值始终>=0)。
² 如果游戏者处在一个点x,g(x)=0。那么游戏者无论如何移动,下一个点的SG值都不等于0。
² 对于n个棋子,设它们对应的顶点的SG值分别为(a1,a2,…,an),再设局面(a1,a2,…,an)时的Nim游戏的一种必胜策略是把某个ai变成k,那么原游戏的一种必胜策略就是把第i枚棋子移动到一个SG值为k的顶点。这听上去有点过于神奇——怎么绕了一圈又回到Nim游戏上了。
² 可以用如下方式定义组合游戏的和:初始局面包含每个子游戏的初始局面,而每次每个游戏者可以任意选一个游戏,进行一次合法走步,而让其他游戏局面保持不变。
² 3堆火柴的Nim游戏可以作看是3个一堆火柴的Nim游戏之和。一堆Nim游戏是简单的:直接把所有火柴拿掉即可;但3堆火柴却复杂得多。看样子,即使每个游戏都很简单,它们的和也可能很复杂。
² 虽然看起来很复杂,但Sprague-Grundy定理提供了一个很好的方案解决这一问题:
² Sprague-Grundy定理:游戏和的SG函数等于各子游戏SG函数的Nim和,即:设gi(x)为Gi的SG函数(1 ≤ i ≤ n),则G = G1+ G2+ … + Gn的SG函数g( x1, x2, …, xn ) = g1(x1)⊕ g2(x2) ⊕ … ⊕ gn(xn)(其中⊕为异或运算)。
² 证明:对于G的任意局面x ( x1, x2, …, xn },设b = g1(x1)⊕ g2(x2) ⊕ … ⊕ gn(xn) ,对于任意一个非负整数a
² (1)都存在x的一个后继局面y,使得g(y)=a;
² (2)不存在x的后继局面y,使得g(y)=b;
² 则b就是满足要求的x的SG函数值,命题也就随之得证。
² 对于事实1,令d=a⊕b,设k是d的二进制位的最高位,即2k-1 ≤ d < 2k。 由于a1(x1)、 g2(x2) 、… 、gn(xn)中至少有一个二进制位第k位为1,不妨设为g1(x1),那么有d⊕g1(x1)< g1(x1),根据SG函数g1的定义,必存在x1的一个后继局面y1,使得g1(y1)=d⊕g1(x1)。
² 故存在{x1,x2,…,xn}的一个后继局面{y1,x2,x3,…,xn},它的SG函数值为:
² g(y1,x2,x3,…,xn)=g1(y1)⊕g2(x2)⊕…⊕gn(xn)
² = d⊕g1(x1)⊕g2(x2)⊕…⊕gn(xn) = d⊕b
² = a⊕b⊕b = a
² 至此事实1成立
² 对于事实2,反设若存在这样的后继局面y,不妨设y={y1,x2,…,xn}(y1是x1的后继局面), 则有:
² g1(x1)⊕g2(x2)⊕…⊕gn(xn) = g1(y1)⊕g2(x2)⊕…⊕gn(xn)
² 即:g1(x1)=g1(y1),这与SG函数g1的性质矛盾。因此事实2成立。
² 由Sprague-Grundy定理可知,对于复杂的公平组合博弈,如果我们可以将它们分解成若干个子游戏的联合,就可以通过求解子游戏的SG函数,方便地求得原游戏的SG函数,从而快速判断胜负情况。
² 有了上面的公平组合博弈的理论为基础,我们就可以来解决引言中提出的问题了。
² 保龄球游戏
² 在一行中有n个木瓶,你和你的朋友轮流用保龄球去打这些木瓶,由于你们都是高手,每一次都可以准确的击倒一个或相邻的两个木瓶,谁击倒最后一个剩余的木瓶谁将获得胜利。如果由你先打,请你分析,你应该采取什么策略来确保赢得胜利?
² 分析:为了看清楚问题的本质,用另一种方式来描述这个游戏。最开始有一堆石子(n个),每一次你可以进行以下四种操作中的一种:
² 1. 从中取出一颗石子;
² 2. 从中取出两颗石子;
² 3. 从一堆中取出一颗石子,并且将这一堆中余下的石子任意分成两堆(每堆至少一颗);
² 4. 从一堆中取出两颗石子,并且将这一堆中余下的石子任意分成两堆(每堆至少一颗)。
² 这四种操作,实际上就依次对应于原来游戏中的以下四种击倒法:
² 1. 击倒一段连续的木瓶中最靠边的一个;
² 2. 击倒一段连续的木瓶中最靠边的连续两个;
² 3. 击倒一段连续的木瓶中不靠边的一个;
² 4. 击倒一段连续的木瓶中不靠边的连续两个。
² 把局面看作顶点,游戏规则看作边,这是一个典型的图游戏。
² 如果当前局面被分成了M堆,(A1,A2,…,Am),则SG(A1,A2,…,Am) = SG(A1)⊕SG(A2)⊕…⊕SG(Am)。对某一堆来说:
² 显然,SG(0)=0。
² 剩余1个时,只能取到0个,而SG(0)=0,所以SG(1)=1。
² 剩余2个时,可以取到0或1,其中SG(0)=0,SG(1)=1,所以SG(2)=2。
² 剩余3个时,我们可以把局面变成1或2或两堆均为1。
² 其中SG(1)=1,SG(2)=2,SG(1, 1)=SG(1) ⊕ SG(1)=0,所以SG(3)=3。
² SG(4)可分解为{SG(2), SG(3),SG(2)⊕SG(1), SG(1)⊕SG(1)} ={2,3,3,0},所以SG(4)=1。
² 对于任意一种局面P,的SG值为SG(P),为了赢得胜利,我们只需将他的局面变成Q,使得SG(Q)=0即可。
² 对于每一个N值,我们为了求出他的SG值,时间复杂度为O(N) ,如果只要求你求出你第一步应该如何行动,那么这种普通的方法需要O(N2)的复杂度,显然不能令我们满意。
² 这个问题看似已经解决,但我们可以进行优化。事实上,我们通过观察较小的数的SG值,可以发现:
² 0 ~11的SG值为:0 1 2 3 1 4 3 2 1 4 2 6
² 12~23的SG值为:4 1 2 7 1 4 3 2 1 4 6 7
² 24~35的SG值为:4 1 2 8 5 4 7 2 1 8 6 7
² 36~47的SG值为:4 1 2 3 1 4 7 2 1 8 2 7
² 48~59的SG值为:4 1 2 8 1 4 7 2 1 4 2 7
² 60~71的SG值为:4 1 2 8 1 4 7 2 1 8 6 7
² 72~83的SG值为:4 1 2 8 1 4 7 2 1 8 2 7
² 并且从72开始,SG值以12为循环节,不断的重复出现,这样我们求出所有SG值的复杂度就降到了常数,这样判断第一步的如何选择的复杂度就降为了O(N)。
² 具体问题具体分析
² 也有很多题目不能完全照搬上述模型的,这就需要具体问题具体分析。来看2011年湖南省程序设计竞赛的题目E:
² 盒子游戏
² 题目大意:
² 有两个相同的盒子,其中一个装了n个球,另一个装了一个球。Alice和Bob发明了一个游戏,规则如下:Alice和Bob轮流操作,Alice先操作。每次操作时,游戏者先看看哪个盒子里的球的数目比较少,然后清空这个盒子(盒子里的球直接扔掉),然后把另一个盒子里的球拿一些到这个盒子中,使得两个盒子都至少有一个球。如果一个游戏者无法进行操作,他(她)就输了。
² 面对两个各装一个球的盒子,Bob无法继续操作,因此Alice获胜。你的任务是找出谁会获胜。假定两人都很聪明,总是采取最优策略。
² 输入
² 输入最多包含300组测试数据。每组数据仅一行,包含一个整数n(2 ≤ n ≤ 109)。输入结束标志为n=0。
² 输出
² 对于每组数据,输出胜者的名字。
² 样例输入
² 2
² 3
² 4
² 0
² 样例输出
² Alice
² Bob
² Alice
² 分析:
² 这个题目,无法套用巴什博弈、威佐夫博弈、斐波那契博弈、尼姆博弈这四个常见模型,和清空/分割游戏也有差别。如果用万能的alpha-beta剪枝算法绝对超时。怎么办?不妨耐心的仔细分析,找出规律,因为竞赛题都是应试教育下的变态产物(不过也能培养一顶的能力),你要做出这类题,第一是要有耐心,第二是你的思维要比出题的刘汝佳之流更加变态才行。
² 仔细分析,可以发现,既然球数量少的盒子里的球都是要丢弃的,这道题可以转换为,在一个剩下n个球的盒子里,拿走k个球,其中1 ≤ k ≤ int(n/2),还剩下int(n – k)个球。如果某个游戏者面对盒子里的球只剩下1个,即n=1时,就败了。引入函数win(n),表示这个盒子剩下的球数为n时,当前游戏者是否能获得必胜态,由于不存在平局,当win(n)=1时,当前游戏者能获得必胜态,当win(n)=0时由于对手足够聪明,当前游戏者只能获得必败态。所以:
² n=1时,根据游戏规则,对当前游戏者来说是必败态,即win(1) = 0。
² n>1时,对当前游戏者拿走k个球,还剩下r个球留给对方,r = n – k,其中1 ≤ k ≤ (int)(n/2),这样,r就有个范围,即int((n + 1)/2) ≤ r ≤ n – 1。博弈树是一种特殊的与或树,“或”结点和“与”结点是逐层交替出现的。己方扩展的结点之间是“或”关系,对方扩展的结点之间是“与”关系(关于博弈树的这段话关紧要,看不懂的同学可以掠过,看得懂的同学能在瞬间明白是怎么回事)。所以只要win(int((n + 1)/2))、win(int((n + 1)/2) + 1)、win(int((n + 1)/2) + 2)、…、win(n – 1)中所有的值是1,那么对方都必胜,自己就只能获得必败态;但是,只要win(int((n + 1)/2))、win(int((n + 1)/2) + 1)、win(int((n + 1)/2) + 2)、…、win(n – 1)中存在某个值是0,那么自己可以获得必胜态。
² 换句话来说:
² 一旦发现某个r值使得win(r) = 0,则从win(r + 1)到win(2 * r)的值都是1;
² 一旦发现win(r + 1)到win(2 * r)的值都是1,则win(2 * r + 1) = 0;
² ……
² 上述分析很重要,搞明白上述分析后,
² r = 1时,已知win(1) = 0,能推出win(2) = 1,以及win(3) = 0。
² r = 3时,由win(3) = 0,又能推出win(4) = win(5) = win(6) = 1,以及win(7) = 0。
² r = 7时,由win(7) = 0,又能推出win(8) = win(9) = … = win(14) = 1,以及win(15) = 0。
² r = 15时,由于win(15) = 0,又能推出win(16) = win(17) = … = win(30) = 1,以及win(31) = 0。
² r = 31时,由于win(31) = 0,又能推出win(32) = win(33) = … = win(62) = 1,以及win(63) = 0。
² ……
² 自此,由数学归纳法可以证明,只有n = (int)pow(2, m) – 1时(m为非负整数),win(n) = 0,当前游戏者面临必败态,除此之外,当前选手都能面临必胜态!
² 求win函数的代码我写了一个,只有寥寥数行。
² 农大公布的标准程序采用的是递归的方法,我个人认为没有我上面的算法效率高,我的算法实质上就是判断n+1是不是构成2的某个非负整数次方幂。
² 猪县长同学写的程序可能效率更高,他采用的是预制表的办法,将问题规模内的2的所有整数次方幂-1的值放在一个向量或者数组中,然后用类似二分查找的办法去找,找到了就是先手必胜,否则先手必败。
² #include
² using namespace std;
² int Win(int n)
² {
² int m = log(n + 1) / log(2);
² int k = pow(2, m);
² if(k == n + 1) return 0; //先手必败
² return 1; //先手必胜
² }
² int main()
² {
² int n;
² cin >> n;
² while( n > 0)
² {
² if(Win(n)) cout << “Alice\n”;
² else cout << “Bob\n”;
² cin >> n;
² }
² return 0;
² }