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)]這一堆石子,且後手先取)的必敗態,即先手一定可以取到這一堆的最後一顆石子。
同理可知,對於以後的每一堆,先手都可以取到這一堆的
最後一顆石子,從而獲得遊戲的勝利。