zoukankan      html  css  js  c++  java
  • 博弈游戏汇总

    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

  • 相关阅读:
    springcloud 微服务 分布式 Activiti6 工作流 vue.js html 跨域 前后分离
    java 整合redis缓存 SSM 后台框架 rest接口 shiro druid maven bootstrap html5
    继承
    封装
    对象的生命周期
    类与对象
    如何理解类?
    面向过程
    jdk1.8新特性
    git使用指南
  • 原文地址:https://www.cnblogs.com/wintersong/p/4393189.html
Copyright © 2011-2022 走看看