博弈游戏汇总
1、巴什博弈
一堆石子,有n个,两个人轮流取,每次至少取1个,至多取m个,拿走最后一个石子的人获胜
假设一堆石子有 n=m+1 由于一次只能取m个,无论先手取多少个,后手总能拿走剩余的,这时一定是先手负
于是找到取胜规则:
一对石子 n=(m+1)*r+s
对于先手应该先取走s个,设后手取走k个,先手再取走 m+1-k 剩余的石子个数为 (m+1)(r-1) 以后保持这样的取法,先取者获胜
总之,就是要留给对手 m+1的倍数
可以归结为: s=0时,后手胜
s<>0时,先手胜
2、简单的石子游戏
有n堆石子,每次至少取一根,至多拿走整堆,两人轮流拿,每次限拿其中一堆,取走最后一根的获胜。
1902年获胜策略已由美国数学家C.L.Bouton分析完成,用到的是二进制和平衡状态概念。其结论是:
对于n堆石子,第i (1<=i<=n)堆石子的个数是Xi,该状态为必败状态当且仅当 X1 XOR X2 XOR……Xn=0。
看个例子:
有4堆石子,数量分别为:7 9 12 15
二进制形式为
0111
1001
1100
1111
异或结果为:1101
1101^1001=0100=4 可以从第二堆拿走5个
1101^1100=0001=1 也可以从第三堆拿走11个
1101^1111=0010=2 或者从第四堆取走13个
比如这道题:http://www.acmicpc.sdnu.edu.cn/Problem.aspx?pid=1294
给定N堆石子,两人轮流取石子,必须先取完一堆石子才能取另一堆,而且另一堆石子的个数必须比之前取的那一堆小,每次只能取1个或者质数个石子。如果没有石子可以取了,那么他就输了。问先手是否有必胜策略。
其实对于这个游戏,我们只需要判断第一堆石子即可,也就是石子数目最少的那一堆
代码:
#include<iostream> #include<algorithm> #include<math.h> using namespace std; int isprime(int n){ int s=(int)sqrt(n); if(n==1) return 1; for(int i=2;i<=s;i++){ if(n%i==0) return 0; break; } return 1; } int main(){ int n,a[100000],x,i,j,t; while(cin>>n){ for( i=0;i<n;i++) cin>>a[i]; //每堆石子的数目 sort(a,a+n); for(j=1;j<=a[0];j++){ if(isprime(j)&&j<=a[0]) a[0]=a[0]-j; x=0; for(t=0;t<n;t++) x^=a[t]; if(x==0){ cout<<"yes"<<endl; break; }else a[0]=a[0]+j; //恢复石子数目 } if(x!=0) cout<<"no"<<endl; } return 0; }
3、Nim游戏
有n堆石子,每堆石子的数量为 x1, x2, x3,x4......xn。给定k个数a1,a2,a3,……ak 两人轮流取,每人每次只能选取一堆,
从中取出一些,每次所取的数量一定在a1,a2,a3,……ak中,拿走最后一根的人获胜。
我们可以将游戏分解,把每一堆看成是一个子游戏。
比如,有3堆石子,每堆石子的数目,为5,6,7,可以取的石子的数目是 {1,3,4}
可以找出每堆石子的所有后继状态,看成是n枚棋子在有向图上移动,甲乙双方人选一个子游戏并移动上面的棋子。
看起来状态非常多,也很复杂,所以我们需要借鉴SG函数
首先定义一个mex 运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于一个给定的有向无环图,关于每个顶点的SG函数定义如下:
g(x)=mex{g(y)|y是x的所有后继状态};没有出边的顶点,SG值为0,因为它的后继集合为空。
对于n枚棋子,它们对应顶点的SG值分别为:(a1,a2,……,an)再设局面 (a1,a2,……,an)时Nim游戏的一种必胜策略是 将ai变成k,那么游戏的一种必胜策略就是
把第i枚棋子移动到SG值为k的顶点上;
#include<iostream> using namespace std; /*输入堆数 n,每堆的石子数a[] 输入限定选择的数目 k, 是s[] */ int max(int a[],int n){ int m=0; for(int i=0;i<n;i++){ if(m>a[i]) m=a[i]; } return m; } int main(){ int n,a[100],i,j,s[100],k,*vis,*grund; while(cin>>n){ for(i=0;i<n;i++) cin>>a[i]; int size=max(a,n); vis=new int[size+1]; grund=new int[size+1]; grund[0]=0; cin>>k; for(i=0;i<k;i++) cin>>s[i]; // for(i=1;i<=size;i++){ //i是石子的数目,a[j]是每次应当取的石子数 i-a[j] 剩余的石子数 memset(vis,0,sizeof(vis)); for(j=0;j<k;j++){ if(s[j]<=i){ vis[grund[i-s[j]]]=1; } for(int k=1;;k++){ if(vis[k]==0) grund[i]=k; break; } } } int x=0; for(i=0;i<n;i++) x^=grund[a[i]]; if(x==0) cout<<"先手胜"<<endl; else cout<<"后手胜"<<endl; } return 0; }
很好的博客对博弈的解释:http://www.cppblog.com/MiYu/archive/2012/08/14/124649.html#187193