博弈游戏汇总

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;
}
View Code

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;
}
View Code


 

 很好的博客对博弈的解释:http://www.cppblog.com/MiYu/archive/2012/08/14/124649.html#187193

原文地址:https://www.cnblogs.com/wintersong/p/4393189.html