基本博弈論(四大博弈+拓展nim)

一.  巴什博奕(Bash Game):

      A和B一塊報數,每人每次報最少1個,最多報4個,看誰先報到30。比如第一次報數,A報k個數,那麼B報5-k個數,那麼B報數之後問題就變爲,A和B一塊報數,看誰先報到25了,進而變爲20,15,10,5,當到5的時候,不管A怎麼報數,最後一個數肯定是B報的,可以看出,作爲後手的B在個遊戲中是不會輸的。

原理:如果n=m+1,那麼由於一次最多隻能取m個,所以,無論先取者拿走多少個,後取者都能夠一次拿走剩餘的物品,後者取勝。也就是把全部數以m+1個爲一組分開。我們可以找到這麼一個整數x和r,使n=x*(m+1)+r,當(m+1) | n時,也就是r=0時,後手會贏

#include <iostream>
using namespace std;
int main()
{
    int n,m;
    while(cin>>n>>m)
      if(n%(m+1)==0)  cout<<"後手必勝"<<endl;
      else cout<<"先手必勝"<<endl;
    return 0;
}
 

 

二.  尼姆博弈(Nimm Game):

尼姆博弈指的是這樣一個博弈遊戲:有任意堆物品,每堆物品的個數是任意的,雙方輪流從中取物品,每一次只能從一堆物品中取部分或全部物品,最少取一件,取到最後一件物品的人獲勝。取完最後一個爲勝!

我們假設有三堆物品用(a,b,c)來表示。我們用(a,b,c)表示某種局勢,其中a,b,c分別表示當你選擇時每一堆剩餘的物品數。
.無論誰面對某種局勢時,都必敗的話,我們稱這種局勢爲奇異局勢。我們先來看奇異局勢:
  ①首先(0,0,0)顯然是奇異局勢。
  ②第二種奇異局勢是(0,n,n)。因爲在這種情況下無論你拿走多少件物品,對方都可以拿走和你一樣多的物品,這樣你必敗
  ③其次(1,2,3)也是奇異局勢。因爲無論你怎麼拿,對手都可以將它變爲(0,n,n)格式。

任何奇異局勢(a,b,c)都有a xor b xor c =0。該結論可以推廣至若干堆,都是成立的。

如果我們面對的是一個非奇異局勢(a,b,c),要如何變爲奇異局勢呢?假設a < b< c,我們只要將c 變爲a xor b,即可,因爲有如下的運算結果: a xor b xor (a xor b)=(a xor a) xor (b xor b)=0 xor 0=0.要將c 變爲a xor b,只要從c中減去c-(a xor b)即可.

也就是判斷a xor b xor c 是否爲0,如果爲零,則先手必敗,否則先手 必勝

#include <cstdio>
#include <cmath>
#include <iostream>
using namespace std;
int main()
{
    int n,ans,temp;
    while(cin>>n)
    {
        temp=0;
        for(int i=0;i<n;i++)
        {
            cin>>ans;
            temp^=ans;
        }
        if(temp==0)  cout<<"後手必勝"<<endl;
        else cout<<"先手必勝"<<endl;
    }
    return 0;
}

取完最後一個爲敗!

#include<iostream>
#include<cstdio>
using namespace std;

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        int ans=0;
        int flag=0;
        for(int i=0;i<n;i++)
        {
            int x;
            scanf("%d",&x);
            ans^=x;
            if(x>1)
                flag++;
        }
        cout<<"ans:"<<ans<<' '<<flag<<endl;
        if((ans&&!flag)||(!ans&&flag))//當奇數個1 或 有多個相同的偶數 時先手一定輸
            printf("Brother\n");
        else
            printf("John\n");
    }
    return 0;
}

給出n堆石子,每堆都有相應個數,最後問先手一開始有多少種取子方式能夠取得最終的勝利。

其實把異或和算出來,然後用異或和去異或每個堆(因爲偶數次異或,相當於抹去了這個值,比如 1^2==3,1^2^2=1,相當於抹去了2),也就是看這個堆對最終的異或和有沒有貢獻,若有貢獻,則這是一種走法

#include<iostream>
using namespace std;
int a[1005];
int main()
{
    int n;
    while(cin>>n&&n)
    {
        int ans=0;
        int Count=0;
        for(int i=0;i<n;i++)
        {
            cin>>a[i];
            ans^=a[i];
        }
        for(int i=0;i<n;i++)
        {
            if((ans^a[i])<a[i])//如果對ans是有貢獻
                Count++;//如果有 則方法數+1
        }
        cout<<Count<<endl;
    }
}

Nim Staircase博奕:

這個問題是尼姆博弈的拓展:遊戲開始時有許多硬幣任意分佈在樓梯上,共n階樓梯從地面由下向上編號爲0到n。遊戲者在每次操作時可以將樓梯j(1<=j<=n)上的任意多但至少一個硬幣移動到樓梯j-1上。遊戲者輪流操作,將最後一枚硬幣移至地上(0號)的人獲勝。

       其實階梯博弈經過轉換可以變爲Nim..把所有奇數階梯看成N堆石子..做nim..把石子從奇數堆移動到偶數堆可以理解爲拿走石子..就相當於幾個奇數堆的石子在做Nim 。假設我們是先手...所給的階梯石子狀態的奇數堆做Nim先手能必勝...我就按照能贏的步驟將奇數堆的石子移動到偶數堆...如果對手也是移動奇數堆..我們繼續移動奇數堆..如果對手將偶數堆的石子移動到了奇數堆..那麼我們緊接着將對手所移動的這麼多石子從那個奇數堆移動到下面的偶數堆...兩次操作後...相當於偶數堆的石子向下移動了幾個..而奇數堆依然是原來的樣子...即爲必勝的狀態...就算後手一直在移動偶數堆的石子到奇數堆..我們就一直跟着他將石子繼續往下移..保持奇數堆不變...如此做下去..我可以跟着後手把偶數堆的石子移動到0..然後你就不能移動這些石子了...所以整個過程..將偶數堆移動到奇數堆不會影響奇數堆做Nim博弈的過程..整個過程可以抽象爲奇數堆的Nim博弈...

結論:將奇數樓層的狀態異或,和爲0則先手必敗,否則先手必勝。

#include<iostream>
#include<cmath>
using namespace std;
int num[100];
int main()
{
    int n;
    cin>>n;
    int temp=0;
    for(int i=0;i<n;i++)
    {
        cin>>num[i];
        if(i&1)//奇數堆
            temp^=num[i];
    }
    if(temp)
        cout<<"先手必勝"<<endl;
    else
        cout<<"先手必敗"<<endl;
}

 

 

 

三.  威佐夫博弈(Wythoff Game):

有兩堆各若干個物品,兩個人輪流從某一堆或同時從兩堆中取同樣多的物品,規定每次至少取一個,多者不限,最後取光者得勝.

直接說結論了,若兩堆物品的初始值爲(x,y),且x<y,則另z=y-x;記w=(int)[ ( (sqrt(5)+1) /2 )*z ];若w=x,則先手必敗,否則先手必勝。

#include<iostream>
#include<cmath>
using namespace std;
int main()
{
    int x,y;
    cin>>x>>y;
    int z=abs(y-x);
    if((int)(((sqrt(5)+1)/2)*z)==min(x,y))
        cout<<"後手必勝"<<endl;
    else
        cout<<"先手必勝"<<endl;
}

 

四.  斐波那契博弈:

有一堆物品,兩人輪流取物品,先手最少取一個,至多無上限,但不能把物品取完,之後每次取的物品數不能超過上一次取的物品數的二倍且至少爲一件,取走最後一件物品的人獲勝。

結論是:先手勝當且僅當n不是斐波那契數(n爲物品總數)

#include<iostream>
#include<cmath>
using namespace std;
int f[100];
void Init()//斐波那契數列
{
    f[0]=f[1]=1;
    for(int i=2;i<=100;i++)
    {
        f[i]=f[i-1]+f[i-2];
    }
}

int main()
{
    int n;
    cin>>n;
    Init();
    bool flag=false;
    for(int i=0;f[i];i++)
    {
        if(f[i]==n)
        {
            flag=true;
            break;
        }
    }
    if(flag)
        cout<<"後手必勝"<<endl;
    else
        cout<<"先手必勝"<<endl;
}

 

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