51node-Bash game V1V2V3V4

51node-Bash gameV1V2V3V4

Bash game v1:

有一堆石子共有N個。A B兩個人輪流拿,A先拿。每次最少拿1顆,最多拿K顆,拿到最後1顆石子的人獲勝。假設A B都非常聰明,拿石子的過程中不會出現失誤。給出N和K,問最後誰能贏得比賽。
例如N = 3,K = 2。無論A如何拿,B都可以拿到最後1顆石子。

解:
最基本的Bash game,顯然,只有n % (k+1) == 0時B才能贏。否則A將剩餘的石子取到%(k+1)==0的數字,每次只要取[k+1-(B取得數字)]即可獲勝。

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

int main()
{
    int n, k, T;
    cin>>T;
    while (T--) {
      cin>>n>>k;
      if (n % (k+1) == 0) cout<<"B"<<endl;
      else cout<<"A"<<endl;
    }
    return 0;
}

Bash game V2

有一堆石子共有N個。A B兩個人輪流拿,A先拿。每次只能拿1,3,4顆,拿到最後1顆石子的人獲勝。假設A B都非常聰明,拿石子的過程中不會出現失誤。給出N,問最後誰能贏得比賽。
例如N = 2。A只能拿1顆,所以B可以拿到最後1顆石子。

解:
我們先要知道:
(1) SG函數:對於任意狀態的x,它的SG函數值g(x)=mex{g(y)|y是x的後續狀態}。其中mex是一個對於非負整數集的運算,mex(S)爲S中沒有出現的最小正整數。對於一個終止狀態,它沒有後續狀態,所以它的SG函數值爲0.
(2)終止狀態:即當前狀態下游戲不能繼續進行。
(3)對於一個狀態,如果這個狀態的SG值==0,那麼這個狀態是P態,即當前一手必敗,否則就是N態,即當前一手必勝

於是,我們用Si表示還剩i個物品的狀態。
g(s0)=0
g(s1)=mex{g(s0)}=1
g(s2)=mex{g(s1)}=0
g(s3)=mex{g(s2),g(s0)}=1
g(s4)=2
g(s5)=3
g(s6)=2
~~~~~~~~~~~~~~~~~~~
g(s7)=0
g(s8)=1
g(s9)=0
g(s10)=1
……
發現循環節

#include <iostream>
using namespace std;

int main()
{
    int T, n, p[10] = {0, 1, 0, 1, 2, 3, 2};
    cin>>T;
    while (T--) {
      cin>>n;
      if (p[n % 7]) cout<<"A"<<endl;
      else cout<<"B"<<endl;
    } 
    return 0;
}

Bash game V3

有一堆石子共有N個。A B兩個人輪流拿,A先拿。每次拿的數量只能是2的正整數次冪,比如(1,2,4,8,16….),拿到最後1顆石子的人獲勝。假設A B都非常聰明,拿石子的過程中不會出現失誤。給出N,問最後誰能贏得比賽。
例如N = 3。A只能拿1顆或2顆,所以B可以拿到最後1顆石子。(輸入的N可能爲大數)

解:同Bash gameV2

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAXN = 1020;

char num[MAXN];

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
      int sum = 0;
      scanf("%s", num);
      int len = strlen(num);
      for (int i = 0; i < len; i++) 
        sum += num[i] - '0';
      if (sum % 3) printf("A\n");
      else printf("B\n");
    }
    return 0;
}

Bash game V4

有一堆石子共有N個。A B兩個人輪流拿,A先拿。每次拿的數量最少1個,最多不超過對手上一次拿的數量的2倍(A第1次拿時要求不能全拿走)。拿到最後1顆石子的人獲勝。假設A B都非常聰明,拿石子的過程中不會出現失誤。給出N,問最後誰能贏得比賽。
例如N = 3。A只能拿1顆或2顆,所以B可以拿到最後1顆石子。

解:這裏就不能用SG函數找規律了,於是,我選擇暴力打表找規律。將最後A勝定義遊戲值爲1,B勝爲-1,輪到A走即爲Max狀態,否則爲Min狀態。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 100000;

int n, tep, ans;

struct Status{
    int less, now, fa, got, id, val;
    void init(int a, int b, int c, int d, int e, int f){
        less = a, now = b, fa = c, got = d, id = e, val = f;
    }
    void print(){
        printf("編號:%d ## fa是:%d, 這一步是%d走,還剩%d棋子,父節點取走%d個,這個節點的val是%d\n", id, fa, now, less, got, val);
    }
}Sta[MAXN];

int dfs(Status& x){
    //這一步就是終止狀態
    if (x.got * 2 >= x.less && x.fa != -1) {
      if (x.now == 1) x.val = 1;
      else x.val = -1;
      return x.val; 
    } 
    //初始化x.val的值 
    if (x.now) x.val = -1;
    else x.val = 1;
    for (int i = 1; i <= min(x.got * 2, n-1); i++) {
      int flag = ++tep;
      Sta[flag].init(x.less-i, x.now^1, x.id, i, flag, 0);
      if (x.now)    //MAX節點 
        x.val = max(dfs(Sta[flag]), x.val);
      else          //MIN節點 
        x.val = min(dfs(Sta[flag]), x.val);
    }
    return x.val;
}

int main()
{
    for (int i = 2; i <= 21; i++) {
      n = i, tep = 1;
      Sta[1].init(n, 1, -1, 10000, 1, 0);
      if (dfs(Sta[1]) == 1) printf("%d : A\n", i);
      else if (Sta[1].val == -1) printf("%d : B\n", i);
    }
    return 0;
}

通過對博弈樹的搜索(這裏其實可以用alpha-beta剪枝),我們發現這是一個fibonacci博弈。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <set>
using namespace std;

set<int> fib;

int main()
{
    //init---------
    int a = 1, b = 1, c;
    fib.insert(1);
    while (c <= 1e9) {
      c = a;
      a = b;
      b = c + b;
      fib.insert(b);
    }
    //--------------
    int n, T;
    cin>>T;
    while (T--) {
      cin>>n;
      if (fib.count(n)) cout<<"B"<<endl;
      else cout<<"A"<<endl;
    }
    return 0;
} 

對其證明(引用自http://www.cnblogs.com/Ritchie/p/5762320.html

用第二數學歸納法證明:
爲了方便,我們將n記爲f[i]。
1、當i=2時,先手只能取1顆,顯然必敗,結論成立。
2、假設當i<=k時,結論成立。
則當i=k+1時,f[i] = f[k]+f[k-1]。
則我們可以把這一堆石子看成兩堆,簡稱k堆和k-1堆。
(一定可以看成兩堆,因爲假如先手第一次取的石子數大於或等於f[k-1],則後手可以直接取完f[k],因爲f[k] < 2*f[k-1])
對於k-1堆,由假設可知,不論先手怎樣取,後手總能取到最後一顆。下面我們分析一下後手最後取的石子數x的情況。
如果先手第一次取的石子數y>=f[k-1]/3,則這小堆所剩的石子數小於2y,即後手可以直接取完,此時x=f[k-1]-y,則x<=2/3*f[k-1]。
我們來比較一下2/3*f[k-1]與1/2*f[k]的大小。即4*f[k-1]與3*f[k]的大小,對兩值作差後不難得出,後者大。
所以我們得到,x<1/2*f[k]。
即後手取完k-1堆後,先手不能一下取完k堆,所以遊戲規則沒有改變,則由假設可知,對於k堆,後手仍能取到最後一顆,所以後手必勝。
即i=k+1時,結論依然成立。

那麼,當n不是Fibonacci數的時候,情況又是怎樣的呢?
這裏需要藉助“Zeckendorf定理”(齊肯多夫定理):任何正整數可以表示爲若干個不連續的Fibonacci數之和。
關於這個定理的證明,感興趣的同學可以在網上搜索相關資料,這裏不再詳述。
分解的時候,要取儘量大的Fibonacci數。
比如分解85:85在55和89之間,於是可以寫成85=55+30,然後繼續分解30,30在21和34之間,所以可以寫成30=21+9,
依此類推,最後分解成85=55+21+8+1。
則我們可以把n寫成 n = f[a1]+f[a2]+……+f[ap]。(a1>a2>……>ap)
我們令先手先取完f[ap],即最小的這一堆。由於各個f之間不連續,則a(p-1) > ap + 1,則有f[a(p-1)] > 2*f[ap]。即後手只能取f[a(p-1)]這一堆,且不能一次取完。
此時後手相當於面臨這個子游戲(只有f[a(p-1)]這一堆石子,且後手先取)的必敗態,即先手一定可以取到這一堆的最後一顆石子。
同理可知,對於以後的每一堆,先手都可以取到這一堆的
最後一顆石子,從而獲得遊戲的勝利。

發佈了32 篇原創文章 · 獲贊 1 · 訪問量 3983
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章