博弈-組合遊戲

組合遊戲:

規則1:一個狀態是必敗的狀態,當且僅當它的所有後繼狀態爲必勝狀態

        規則2:一個狀態是必勝的狀態,當且僅當它的所有後繼狀態中至少有一個是必敗狀態

1.Ferguson遊戲:

兩個盒子有石子n,m。遊戲規則爲選擇其中一個盒子清空,把另一個盒子的石子拿一些給清空的盒子,但需保證至少都有一個。。終態爲[1,1]

狀態:
從後向前推,[n,m]狀態能推出的有一個爲必敗,則[n,m]必勝
                        [n,m]能推出的全爲必勝,則[n,m]必敗


#include <iostream>
using namespace std;
#define maxn 100
int win[maxn][maxn];
int main(){
    win[1][1]=false;
    for(int k=3;k<20;k++){
        for(int n=1;n<k;n++){
            int m=k-n;
            win[n][m]=false;
            for(int i=1;i<n;i++)
                if(!win[i][n-i])win[n][m]=true;
            for(int i=1;i<m;i++)
                if(!win[i][m-i])win[n][m]=true;
            if(n<=m && !win[n][m]) cout<<n<<" "<<m<<endl;
        }
    }

return 0;}

//兩個盒子有石子n,m。遊戲規則爲選擇其中一個盒子清空,把另一個盒子的石子拿一些給清空的盒子,但需保證至少都有一個。。終態爲[1,1]

//從後向前推,[n,m]狀態能推出的有一個爲必敗,則[n,m]必勝
//                       [n,m]能推出的全爲必勝,則[n,m]必敗

Chomp!遊戲:

有一個m×n的棋盤,每次可以取走一個方格並它右邊和左邊的所有棋子,取到最後拿到(1,1)的輸


結論:除了(1,1)先手必敗,其他情況必勝:

證明:

假設當前先手去了最右上角的那個方格,則後手選擇了(k1,k2)進入了必勝狀態,則顯然先手剛開始也可以取(k1,k2)進入必勝狀態。於是矛盾


約數遊戲:

有1~n個數,兩個人輪流取一個數並它所有的約數抹去,最後取完的勝;

顯然先手一直必勝:

證明:假設先手先取了1,然後後手去了k進入必勝狀態,顯然先手開始也可以取k進入該必勝狀態,因此矛盾


nim博弈:

結論:如果有x1^x2^...^xn=X,若X=0則必敗,否則必勝

1.對於沒有火柴的狀態,顯然是必敗狀態

2.對於必勝狀態,必能推出必敗狀態:

證明:假設X不爲0,且X的最高位(二進制)在第k位,顯然xi中至少會有一個Y,第k位也爲1(否則異或運算時,第k位的1沒法獲得),則第一次取的時候在Y上拿成Z=Y^X

顯然Z<Y成立,因爲異或運算只改變了Y的第k位的1和後面的部分,前面的高位未動,相比之前變小了。拿成Z之後,在進行運算,即X^Z^Y=X^X=0;

3.對於必敗的狀態,推出的都是必勝狀態

證明:必敗時,X=0,因爲只能取一堆,因此無論怎麼取,都只能使X非0;


組合遊戲的和:

假設有K個組合遊戲,G1 G2 G3  ... Gn。每次可以選擇任一個進行,其他局面不變。

解決辦法:SG函數和SG定理:

SG(x)=mex(S),S是x的後繼狀態的SG函數值集合,mex(S)表示不在S內的最小非負整數;

遊戲和的SG函數等於各子游戲SG函數的NIM和


石子游戲:n堆石子,只能取 至少一個,且不能超過一半,最後不能取得輸。


1.先算一下SG函數:


#include <iostream>
#include <string.h>
using namespace std;
#define maxn 110
int sg[maxn];
int vis[maxn];
int main(){
    sg[1]=0;
    for(int i=2;i<maxn;i++){
        memset(vis,0,sizeof(vis));
        for(int j=1;j*2<=i;j++)vis[sg[i-j]]=1;
        for(int j=0;;j++){
            if(!vis[j]){
                sg[i]=j;
                break;
            }
        }
        cout<<sg[i]<<" ";
    }
    cout<<endl;
return 0;}

(從2開始)

1 0 2 1 3 0 4 2 5 1 6 3 7 0 8 4 9 2 10 5 11 1 12 6 13 3 

可以得出結論:

sg(n)=sg(n/2);n爲奇數

sg(n)=n/2;n爲偶數


於是得到如下程序:

#include <iostream>
#include <string.h>
using namespace std;
#define maxn 110
long long sg(long long x){
    return x%2==0? x/2:sg(x/2);
}
int main(){
    int t;
    cin>>t;
    while(t--){
        int n;
        long long a,v=0;
        cin>>n;
        for(int i=0;i<n;i++){
            cin>>a;
            v^=sg(a);
        }
        if(v)cout<<"yes"<<endl;
        else
            cout<<"no"<<endl;

    }
return 0;}

treblecross遊戲


#include <stdio.h>
#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
int sg[202];//sg[i]表示連續的x個空格子組成的棋盤的SG值
int vis[202];
void init()
{
	int i,j,k;
	sg[0]=0;
	sg[1]=sg[2]=sg[3]=1;
	for(i=4;i<=200;i++)
	{
		memset(vis,0,sizeof(vis));
		for(j=3;j<=5&&i-j>=0;j++) vis[sg[i-j]]=1;
		for(j=6;i-j>=0;j++) vis[sg[j-5]^sg[i-j]]=1;
		for(j=max(i-5,0);j<=i-3;j++) vis[sg[j]]=1;
		for(j=0;j<=200;j++)
            if(!vis[j])
            {
                sg[i]=j;
                break;
            }
	}
}
int e[202],f[202],g[202],n;
int find()
{
    int s=0,i,j,k=0;
    for(i=0;i<n;i++)
    {
        if(f[i]==0)
            k++;
        else
        {
            s=s^sg[k];
            k=0;
        }
    }
    s=s^sg[k];
    return s;
}
int main()
{
    init();
    char a[202];
    int T;
    cin>>T;
    while(T--)
    {
        cin>>a;
        int i,j,k,flag=0,t=0;
        n=strlen(a);
        for(i=0;i<n;i++)
        {
            if(i<n-2&&a[i+1]=='X'&&a[i+2]=='X'){flag=1;e[t++]=i;}
            else if(i>0&&a[i-1]=='X'&&a[i+1]=='X'){flag=1;e[t++]=i;}
            else if(i>1&&a[i-1]=='X'&&a[i-2]=='X'){flag=1;e[t++]=i;}
        }
        if(flag)
        {
            cout<<"WINNING"<<endl;
            cout<<e[0]+1;
            for(i=1;i<t;i++)
                cout<<" "<<e[i]+1;
            cout<<endl;
        }
        else
        {
            memset(g,0,sizeof(g));
            for(i=0;i<n;i++)
                if(a[i]=='X')
                {
                    for(j=i-2;j<=i+2;j++)
                        if(j>=0&&j<n)
                        g[j]=1;
                }
            memcpy(f,g,sizeof(f));
            if(find()==0)
            {
                cout<<"LOSING"<<endl<<endl;
                continue;
            }
            for(i=0;i<n;i++)
            {
                if(g[i]==0)
                {
                    memcpy(f,g,sizeof(f));
                    for(j=i-2;j<=i+2;j++)
                        if(j>=0&&j<n)
                        f[j]=1;
                    if(find()==0)
                        e[t++]=i;
                }
            }
            cout<<"WINNING"<<endl;
            cout<<e[0]+1;
            for(i=1;i<t;i++)
                cout<<" "<<e[i]+1;
            cout<<endl;
        }
    }
    return 0;
}


取石子(十)

時間限制:1000 ms  |  內存限制:65535 KB
難度:6
描述

不知不覺取石子已經到第十道了。地球上的石子也快要取完了!

開個玩笑,當然還是有很多石子的,取石子的題目也是做不完的,今天又來了一道!

有n堆石子,每一堆的規則如下:

第一堆每次只能取2的冪次(即:1,2,4,8,16…);

第二堆只能取菲波那契數列中的元素(即每次只能取1,2,3,5,8…等數量,斐波那契數即後面的數是前面兩個數的和);

第三堆可以取任意多個,最小1個,最多可以把這一堆石子取完;

第四堆只能取1和偶數個(即:1,2,4,6,8,10...);

第五堆只能取奇數個(即:1,3,5,7,9.....);

好吧,這樣下去太煩人了,六堆及其以後每堆最多取的數量爲堆數編號,即第六堆只能取(1,2,3,4,5,6),第七堆只能取(1,2,3,4,5,6,7)....

別看規則很多,但Yougth和Hrdv都是聰明人,現在由Yougth先取,比賽規定誰先取完所有石子既爲勝者,輸出勝者的名字。

輸入
有多組測試數據,每組測試數據開始有一個n。
後面有n個數代表每一堆石子的數量,當n爲0是表示輸入結束。(所有數據小於1000)
輸出
假如Yougth贏輸出“Yougth”,Hrdv贏輸出“Hrdv”。
樣例輸入
6
2 4 2 3 6 7 
樣例輸出
Hrdv



首先:第一石子的sg函數:

#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
#define maxn 100
int sg[maxn];
int vis[maxn];
int main(){
   sg[0]=0;
   cout<<0<<" ";
    for(int i=1;i<maxn;i++){
        memset(vis,0,sizeof(vis));
        
        for(int j=1;j<=i;j*=2){vis[sg[i-j]]=1;}
        for(int j=0;;j++){
            if(!vis[j]){
               sg[i]=j;
                break;
            }
        }
        cout<<sg[i]<<" ";
    }


return 0;}

得出數據分析規律可知:

sg(i)=i%3;

同理程序計算第二堆

(因爲規律較爲複雜,於是直接用函數計算,存儲下來)

第三堆sg函數爲:

sg(i)=i;

第四堆sg函數:

if(i==1 || i==2)
      sg(i)= i;
else  sg(i)= ((i-2)/6)*3+(i-2)%6-1;

第五堆sg函數爲:

sg(i)=i%2;

剩下的第k堆:

sg(i)=(i)%(k+1);


然後套用組合遊戲的規律,把每堆的sg函數值進行NIM異或,得到sum,sum==0 後手贏 sum!=0 先手贏


於是可得以下程序:

#include <iostream>
#include <string.h>
#include <cmath>
using namespace std;
#define maxn 1010
int a[maxn];
int sg[maxn];
void fun1(){
    //int sg[maxn];
    int vis[maxn];
    int a[maxn];
    a[1]=1;a[2]=2;
    for(int i=3;i<maxn;i++)
        a[i]=a[i-1]+a[i-2];
    sg[0]=0;
    for(int i=1;i<maxn;i++){
        memset(vis,0,sizeof(vis));
        for(int j=1;a[j]<=i;j++){vis[sg[i-a[j]]]=1;}
        for(int j=0;;j++){
            if(!vis[j]){
               sg[i]=j;
                break;
            }
        }
    }


}

int fun(int i,int n){
    if(n==1) return i%3;
    else if(n==2){
            if(i==0)
             return 0;
        // int bb[35]={1 ,2 ,3 ,0 ,1 ,2 ,3, 4 ,5 ,0 ,1 ,2 ,3 ,0 ,1 ,2 ,3 ,4 ,5 ,0 ,1 ,2 ,3 ,0 ,1 ,2 ,3 ,4 ,5 ,0 ,1 ,2 ,3 ,4 ,5};
        // return bb[(i-1)%35];
        return sg[i];
    }else if(n==3){
        return i;
    }else if(n==4){
        if(i==0)
          return 0;
        if(i==1 || i==2)
            return i;
        else
            return ((i-2)/6)*3+(i-2)%6-1;
    }else if(n==5){
        return (i)%2;
    }else{
        return (i)%(n+1);
    }
}
int main(){
    fun1();
    int n;
    while(cin>>n,n){
        for(int i=0;i<n;i++){
            cin>>a[i];
        }
        int sum=0;
        for(int i=0;i<n;i++){
            sum^=fun(a[i],i+1);
        }
        if(sum)cout<<"Yougth"<<endl;
        else cout<<"Hrdv"<<endl;
    }
return 0;}





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章