我们在此专题中将考虑这样一类组合游戏:
(1)两个游戏者轮流操作
(2)游戏的状态集有限,并且不管双方怎么走,都不会再出现以前的状态。这保证了游戏在有限步内结束。
(3)谁不能操作谁输,这样的规则避免了平局的出现。
而且我们只考虑公平游戏,即如果一个游戏者可以把状态A变为B,另一个游戏者也可以。国际象棋并不是公平游戏,因为白方可以移动白子,而黑方却不能移动白子。
状态图:为方便描述,我们可以把游戏中的状态画成图。每个节点是一个状态,每条边代表从一个状态转移到另一个状态的操作。
注意:先手必胜状态(简称必胜状态)是指先手存在必胜策略,而不是先手不管怎么走都能赢。
这样不难得出两个规则:
规则一:一个状态是必败状态当且仅当它的所有后继都是必胜状态。
规则二:一个状态是必胜状态当且仅当它至少有一个后继是必败状态。
特例:没有后继状态的状态是必败状态。
因为状态图是无环的,所以我们可以按照拓扑序的逆序来进行判断,在判断每个节点的时候,它的所有后继都已经判断过了。
1.Ferguson游戏
规则:有两个盒子,一开始其中一个盒子有m颗糖,而另一个盒子里有n颗糖,把这样的状态记为(m,n)。每次移动是把其中一个盒子清空而把另一个盒子的一些糖拿到被清空的盒子里,使得两个盒子里分别至少有一颗糖。显然唯一的终态为(1,1)。最后移动的游戏者获胜。那么状态为(m,n)的先手是胜还是败?
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn=100;
int winning[maxn][maxn];
int main()
{
winning[1][1]=false;//如果它的后继状态为(1,1),那么这个人现在的状态为必败状态
for(int k=3;k<20;++k)//我们设k=m+n,这里只对k<20的情况做出了处理
{
for(int n=1;n<k;++n)
{
int m=k-n;
winning[n][m]=false;
for(int i=1;i<n;++i)
{
if(!winning[i][n-i]) winning[n][m]=true;//把原来有m颗糖果的盘子清空
}
for(int i=1;i<m;++i)
{
if(!winning[i][m-i]) winning[n][m]=true;//把原来有n颗糖果的盘子清空
}
}
}
return 0;
}
2.Chomp!游戏
规则:有一个m*n棋盘,每次可以取走一个方格并拿掉它右边和上面的所有方格。拿到左下角的格子(1,1)者输。
给出m和n,问先手必胜还是必败。
分析:本题的结论有点出乎意料,除了(1,1)是先手必败之外,其他情况都是先手必胜。证明如下:
? 如果后手能赢,也就是说后手有必胜策略,使得先手不论第一次取哪个石子,后手都能获得最后的胜利。那么现在 先假设先手取最右上角的石子(n,m),接下来后手通过某种取法使得自己进入必胜局面,然而因为先手第一次取的是(n,m)这个石子,而没有对任何其他石子造成影响,故先手就可以一开始就先于后手选到这个必胜局面,与假设矛盾。
3.约数游戏
规则:有1~n个数字,两个人轮流选则一个数,并把它和它的所有约束擦去。擦去最后一个数的人获胜,问谁会赢。
分析:此题与上题有异曲同工之处,无论n取几,都是先手必胜
? 若后手能赢,先假设先手第一次取1这个数,接下来后手通过某种策略进入必胜状态,然而1依然对其他任何数都没有影响,故先手可以先于后手选择这个数,进入必胜局面,与假设矛盾。
4.Nim游戏
规则:有三堆火柴,分别有a,b,c根,即为状态(a,b,c)。每次一个游戏者可以从任意一堆中拿走至少一根火柴,也可以整堆拿走,但不能同时从多堆火柴中同时拿。无法拿火柴的游戏者输。
之前建立状态图的做法虽然正确,但可惜的是复杂度很高,当a,b,c很大的时候,状态图的结点数和边数会很大。
L.Bouton在1902年的时候给出了这样一个定理,状态(x1,x2,x3)为必败状态,当且仅当x1 xor x2 xor x3=0,这里的xor即为异或,也称为Nim和(Nim sum)。
事实上,这个定理适合于任意堆的情况。,即x1 xor x2 xor...xor xn=0时先手必败。
证明:首先,若每堆火柴都为0,则先手必败;
? 否则,先需要证明两个结论:
? (1)对于必胜状态,一定有一个后继状态是必败的。
? 证明:假设Nim和为X>0,且X的二进制的最左边的1在第k位,则一定存在一个该位为1的堆。设这堆火柴的数量为Y,则只需要把它拿成Z=YxorX根火柴,此时新的Nim和为X xor Y xor(Y xor X)(先在原来的Nim和中去掉火柴数量为Y的那堆火柴,把它变为Z=Y xor X根火柴再加进去)=0。
? (2)对于必败状态,所有后继状态都是必胜的
? 证明:由于只能改变一堆火柴,不管修改它的哪一位,Nim和对应的那一位一定不为0,因此,不可能是必败状态。
至此,Bouton定理成功证明。
*拓展:5.Bash博弈
共n个物品,每人轮流从中取出1~m个物品,最后取物品的游戏者获胜。
分析:若n=m+1,先手先取k个(1<=k<=m),后手总能取完。
进一步我们发现若n=k(m+1)+r,先手先取r个,后手取t(1<=t<=m)个,然后先手再取出m+1-t个,这时候局面就会变为n‘=(k-1)(m+1),接下来,继续这样进行,无论后手取多少个,先手总会把它变为(m+1)的整数倍,最后就会到开始的n=m+1的局面,而整个游戏的先手就相当于此时的后手,故这样的情况时,先手有必胜策略。
总结一下就是若n%(m+1)==0,先手必败,否则先手必胜。
组合游戏的和
假设有n个组合游戏G1,G2,... ,Gn,可以定义一个新游戏,在每个回合中,当前游戏者可以任选一个子游戏Gi进行一次合法操作,而让其他游戏的局面保持不变,不能操作的游戏者输。这个新游戏称为G1,G2,... ,Gn的和。
组合游戏的和通常是很复杂的,为了解决这些问题,我们使用SG函数和SG定理。
SG函数:对于任意状态x,定义SG(x)=mex(S),其中mex(S)表示不在S内的最小非负整数。S是x的后继状态的SG函数值集合。例如x有3个后继状态,SG函数值分别为0,1,1,2,4,7,则SG(x)=3。
这样终态的SG值显然为0(因为S是空集),其他值可递推算出,不难发现,SG值为0当且仅当此状态为必败状态。
单堆Nim游戏的SG函数满足SG(x)=x.
SG定理:游戏和的SG函数等于各子游戏SG函数的Nim和。这样,就可以把各个子游戏分而治之。
例题1:翻棋子游戏
题意:一个棋盘上每个格子有一个棋子,每次操作可以随便选择一个朝上的棋子(x,y)(x行和y列),选择一个形如(x,b)或者(a,y)(其中b<y,a<x)的棋子,然后把它和(x,y)一起翻转,无法操作的人输。
分析:最开始有n个正面朝上的棋子,将其中的棋子(x,y)看做两堆大小为x和y的火柴,共有2*n堆火柴,设其Nim和为K。选择(x,b)和(a,y)是等价的,为了简单起见,我们只考虑选择(a,y)。
若(a,y)正面朝上,则相当于在原来的Nim和中删去x,y,a,y,则新的Nim和为K xor x xor y xor a xor y,因为a<x,故K=K xor (x xor a)相当于把原来大小为x的火柴堆,变为了a大小的火柴堆,相当于删去了(x-a)大小的火柴堆。
若(a,y)反面朝上,则相当于删去x,y,增加a,y,相当于删去(x-a)大小的火柴堆,式子与上面的相同。
例题2:除法游戏
题意:有一个n*m的矩阵(1<=n,m<=50),每个元素均为2~10,000之间的正整数。两个游戏者轮流操作。每次可以选择一行中的1个或多个大于1的整数,把它们中的每个数都变成它们的真因子,例如12可以变为1,2,3,4,6,不能操作的人输。
分析:终态是矩阵中的所有数都是1,这是一个必败状态。考虑每个数含有的素因子个数(比如12=2 * 2 * 3包含3个素因子),则让一个数“变成它的真因子”等价于拿掉它的一个或者多个素因子。这样,每行对应一个火柴堆,每个数的素因子看成一根火柴,则等价于Nim游戏了。
原文地址:https://www.cnblogs.com/iwillenter-top1/p/11616724.html