博弈游戲匯總


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM