背景:
规定每次至少取一个,最多取m个。
两个人足够聪明,最后取光者得胜。
问在 n 确定的情况下如何确定两人的胜败情况。思考过程:
最简单的状态:
状态(1):n=m+1;先手可取1-m个物品,剩余m-1个物品,所以后手必胜;
推论:
状态(2):n=(m+1)+1; 先手只要取1个物品此时转化为状态(1),无论后手取1-m个物品,先手可在第二次取尽,所以先手必胜;(注意状态转化是先后手改变)
状态(3):n=(m+1)+s;先手只要取m个物品即可转化为状态(1),状态(1)结果已知,可直接使用结果推出先手必胜
总结状态(2)(3):
状态(4):n=(m+1)+s;(1<=s<=m); 此时先手只要取s个物品即可转化为状态(1),先手必胜;
状态(5):n=2(m+1);此时无论先手取1-m个,都会转化为转态(4),后手必胜
状态(6):n=2(m+1)+s;此时先手只要取s个物品即可转化为状态(5),先手必胜
状态(7):n=3(m+1);此时无论先手取1-m个都会转化为状态(6),后手必胜
状态(8):n=3(m+1)+s;此时先手只要取s个则转化为状态(7),先手必胜
归纳:
当n=k(m+1)+s时,k=任意自然数,;(1<=s<=m);先手每次只要取掉si个,保证每次给后手留下(m+1)的整数倍,则可必胜;
当n=k(m+1)时,后手必胜;
必胜必败态理论:
所有终结点是必败点
规则1:一个状态是必败的状态,当且仅当它的所有后继状态为必胜状态
规则2:一个状态是必胜的状态,当且仅当它的所有后继状态中至少有一个是必败状态
应用例题:
Ferguson游戏:
进行游戏需要用到两个盒子,在游戏的开始,第一个盒子中有n枚石子,第二个盒子中有m个石子(n, m > 0,n+m<20)。参与游戏的两名玩家轮流执行这样的操作:清空一个盒子中的石子,然后从另一个盒子中拿若干石子到被清空的盒子中(至少一个),使得最后两个盒子都不空。当两个盒子中都只有一枚石子时,游戏结束。最后成功执行操作的玩家获胜。终态为[1,1]
根据必胜必败态理论,[1,1]为必败态,所有只要能推出一个必败态([1,1])的状态为必胜态,所有能推出的子状态都是必胜态的状态为必败态。
代码解:
#include <iostream>
using namespace std;
#define maxn 100
int main(){
bool win[20][20];//win[i][j]记录状态(i,j)是否为必胜态;
win[1][1]=false;//终态(1,1)为必败态;
//将所有状态循环一遍
//因为要将状态(n,m)的子状态全部判断一遍,所以要保证(n,m)的子状态已经全部被判断过
//注意观察状态(n,m)的子状态的特点,一种为i+j=n,一种为i+j=m;
//所以循环时应用n+m的和k做循环条件,从小到大,k最小为2,只有一种状态且已知结果,k为三时子状态的和为2,全部已知结果,k为4时,子状态和为3,2,全部已知结果
//(感觉有点可以类比广度优先或层序遍历的思想呢)
//{动态规划中的自底向上求解子问题}
for(int k=3;k<20;k++){
for(int n=1;n<k;n++){
int m=k-n;
win[n][m]=false;//假设(i,j)为必败态;因为必胜态只要有一个子状态为必败态就可判断,比较好判断;而必败态需要所有子状态都为必胜态,比较难做出判断 ;所以这里我们假设为必败态,去判断是不是必胜态;
//将(n,m)的所有子状态循环一遍;
for(int i=1;i<n;i++){//第一种子状态的和做循环终止条件
if(!win[n-i][i]){//若有一子状态(n-i,i)为必败态
win[n][m]=true;// 则(n,m)为必胜态
break;
}
}
for(int i=1;i<m;i++) {//同理,第二种
if(!win[i][m-i]){//若有一子状态(i,m-i)为必败态
win[n][m]=true;// 则(n,m)为必胜态
break;
}
}
// if(n<m&&win[n][m]==false)cout<<"("<<n<<","<<m<<")"<<endl;//(n,m)(m,n)视为同种状态
//注意,有n<m和n=m两种
if(n<=m&&win[n][m]==false)cout<<"("<<n<<","<<m<<")"<<endl;
}
}
return 0;
}
时间上应该还可以继续优化
2.巴什博弈:
只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。
显然,因此我们发现了如何取胜的法则:如果n=(m+1)r+s,(r为任意自然数,s≤m),那么先取者要拿走s个物品,如果后取者拿走k(≤m)个,那么先取者再拿走m+1-k个,结果剩下(m+1)(r-1)个,以后保持这样的取法,那么先取者肯定获胜。总之,要保持给对手留下(m+1)的倍数,就能最后获胜。
(n-1)%(m+1)==0则后手胜利
先手会重新决定策略,所以不是简单的相反行的
最简单的状态:n=1;先手必败
(1)n=1+s,(1<=s<=m);先手只要取s个,后手必败,先手必胜
(2)n=1+(m+1);无论先手取1-m个,都变为状态(1),先手必败;
(3)n=1+(m+1)+s;先手取s个,变为(2),先手必胜;
(4)n=1+2(m+1);无论先手取1-m个都变为(3),先手必败;
(5)n=1+2(m+1)+s;先手取s个变为(4),先手必胜;
(6)n=1+3(m+1);无论先手取1-m个变为(5),先手必败;
归纳 n=1+k(m+1)(k=任意自然数),先手必败;//所以有(n-1)%(m+1)==0,后手必胜。
n=1+k(m+1)+s;先手必胜;
·nim游戏
“
要知道,像Nim游戏这种博弈问题,最重要的是寻找必败态。这个必败态的的意思就是,这样一种局面摆在面前的话先手必败。其严格定义如下:1.无法进行任何移动的局面是必败态;2.可以移动到必败态的局面是非必败态;3.在必败态做的所有操作的结果都是非必败态。这个还是很好理解的吧,就是自己处在非必败态上总能移动到必败态把必败态留给对方,而对方处在必败态的话总是只能移动到非必败态,把非必败态留给自己,然后自己继续虐对方。
而对于Nim游戏,局面是必败态当且仅当所有堆硬币的数量都异或起来结果为0,即a1^a2^...^an=0!!!为了证明之,我们只要证明它满足上述必败态的三条性质即可。
第一个命题显然,最终局面只有一个,就是全0,异或仍然是0。
第二个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an!=0(不等号就用C++的习惯用!=来表示了),一定存在某个合法的移动,将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'不是一个合法的移动。证毕。
就这样,一个简单而神奇的运算,就搞定了这么个让我绞尽脑汁也毫无头绪的游戏,而Xor运算的出现,又是乍一看完全与问题毫不相干!这正是Xor的奇妙之处,吸引人之处。
”--http://www.physixfan.com/archives/563
PS:正在研读理解组合游戏的问题,顺便整理保留思路,欢迎批评更正。