基本常见的路人皆知的博弈
巴什博奕(Bash Game);威佐夫博奕(Wythoff Game);尼姆博奕(Nimm Game)。 此外,还有翻硬币,删边等。 当然,不乏一些变态数学题。
基础博弈高中是学习过,但是过于基础,现在强化博弈方面。
一般的博弈最后取者胜。自然还有最后取者输的。
博弈的输赢关键在于找到XXX态(必输、必赢、S态?T态之类的东西)。然后看能不能自己面对必赢态,然后取后把必输态留给对手。
注意:一般的博弈题不能普通搜索(直接搜索是否可以再轮到自己时达到某状态)解决,因为两个玩家都是高智商,双方都是采取最优解。
现在的状态应该是:自己可以将面对的状态变为必败,则为必赢;若不能则为必败。 若从一张图上看,就是G状态后序节点中有一个必败节点,则它自己为必胜节点.....然后可能就需要SG函数。
【参考】
博客经典讲解:http://blog.csdn.net/acm_cxlove/article/details/7854526 (cxlove)。
SG函数详细:https://www.cnblogs.com/ECJTUACM-873284962/p/6921829.html
SG函数通俗证明:http://blog.csdn.net/strangedbly/article/details/51137432
二分图博弈:https://www.cnblogs.com/jffifa/archive/2011/12/17/2291069.html (2018-3-25补充)
著名进阶讲解:Thomas S. Ferguson(群里有)。
等做够了题,把难度分级后再上题。
待续-------------------------------------------------------------------------
【基础博弈】
题目大意:
N个硬币排成一排,已知是正面(H)还是反面(T)。现在两个人轮流翻,每次只能选择一枚正面(H)的硬币翻到反面,
同时可以选择一枚左边的硬币翻转(此时不管正反都可以翻)。 没有可以翻的人输。
思路:
我们规定,只有第i个硬币正面向上,则为状态i,全部向下为0。那么状态i可以变为状态0,1,2....i-1。分别表示0:翻转第i个,不翻转左边的;
1:翻转第i个,顺便把第1个也翻转...
但是情况不单单是只有一枚i硬币朝上。但是不重要,如果第j<i枚也朝上,那么翻转第i个,顺便翻转第j个的情况就是j^j=0就行了。
所以状态X可以变为0,1,2,,,,x-1,这和Nim的规则是一样的。

#include<cstdio> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; const int maxn=11000; char chr[maxn]; int main() { int N,Xor,i; while(~scanf("%d",&N)){ scanf("%s",chr+1); Xor=0; for(i=1;i<=N;i++){ if(chr[i]=='H') Xor^=i; } if(Xor) printf("Alice "); else printf("Bob "); } return 0; }
二:John
题意:和Nim一样的规则,但是是取到最后一个石子的人输。

#include<cstdio> #include<cstdlib> #include<iostream> #include<cstring> using namespace std; int main() { int T,g,x,n,i,j; scanf("%d",&T); while(T--){ scanf("%d",&n); x=g=0; for(i=1;i<=n;i++){ scanf("%d",&j); x^=j; g+=(j>1?1:0); } if((!g&&x&1)||(g>1&&!x)) printf("Brother "); else printf("John "); } return 0; }
题意:有一排连续的石子,每次可以取任意连续的石子,取到最后一个的输。
思路:参照上题

#include<cstdio> #include<cstdlib> #include<iostream> #include<cstring> using namespace std; int main() { int g,x,n,i,j; while(~scanf("%d",&n)){ x=g=0; for(i=1;i<=n;i++){ scanf("%d",&j); x^=j; g+=(j>1?1:0); } if((!g&&x)||(g>1&&!x)) printf("No "); else printf("Yes "); } return 0; }
四:Being a Good Boy in Spring Festival
题意:问Nim博弈中,先手胜的情况下,第一步有几种取石子方案。
思路:实验一下每一堆即可。

#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> using namespace std; int p[110]; int main() { int n,Xor,i,ans; while(~scanf("%d",&n)){ if(n==0) return 0; Xor=0; for(i=1;i<=n;i++){ scanf("%d",&p[i]); Xor^=p[i]; } if(!Xor) printf("0 "); else { ans=0; for(i=1;i<=n;i++){ if((Xor^p[i])<=p[i]) ans++; } printf("%d ",ans); } } return 0; }
题意:有N堆石子,Nim规则,但每一堆有上限Li,即单次不能超过Li个。
思路:每一堆mod(Li+1),即Xi>(Li+1)时,利用Bush博弈,后面的再Nim博弈。

#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> using namespace std; int main() { int T,n,Xor,i,ans,M,L; scanf("%d",&T); while(T--){ scanf("%d",&n); Xor=0; for(i=1;i<=n;i++){ scanf("%d%d",&M,&L); Xor^=(M%(L+1)); } if(Xor) printf("No "); else printf("Yes "); } return 0; }
六:取石子游戏
题意:先手不能一次性取完,下一个人取的数量不能超过上一位的两倍。
思路:斐波拉契博弈。“Zeckendorf定理”(齐肯多夫定理):任意数可以表示位多个斐波拉契数的和,而且他们的位置不相邻。 先手每次取后变成的新数,表示成若干个斐波拉契 数的和,从小到大(由于是不相邻的斐波拉契数,后面一个大于前面一个的两倍), 后手都可以取某个斐波拉契数的最后一个。比如先手取后变成21,21=1+3+8;对于“1”, 后手可以取到,对于“3”,先手不能一次性取完,无论怎么取,后手都拿到最后一个,对于“8”也是同理。对于每一个斐波拉契数数,都可以由后手取到。所以斐波拉契数后者胜。
2,3是后手胜利,即斐波拉契数胜。 若不是斐波拉契数,先手可以取部分使之变成斐波拉契数,先手胜。

#include<map> #include<cstdio> #include<iostream> #include<algorithm> using namespace std; map<int,int>mp; int main() { int i,N,a[100]; a[0]=2;a[1]=3; mp[2]=1; mp[3]=1; for(i=2;i<45;i++){ a[i]=a[i-1]+a[i-2]; mp[a[i]]=1; } while(~scanf("%d",&N)){ if(N==0) return 0; if(mp.find(N)!=mp.end()) printf("Second win "); else printf("First win "); } }
【其他博弈】
题意:
两人博弈,给出两个数a和b,较大数减去较小数的任意倍数,结果不能小于0,将两个数任意一个数减到0的为胜者。
思路:假设 a大于b a == b. N态 a%b == 0. N态 a >= 2*b,先手能决定谁取(b,a%b),并且知道(b,a%b)是P态还是N态. N态 b< a< 2*b, 只能 -->(b,a-b) , 然后再进行前面的判断.

#include<cmath> #include<cstdio> #include<cstdlib> #include<iostream> using namespace std; int main() { int num,a,b; while(~scanf("%d%d",&a,&b)){ if(a==0&&b==0) return 0; num=1; while(true){ if(a<b) swap(a,b); if(a%b==0||b==0) break; if(a>=2*b) break; a=a%b; num++; } if(num&1) printf("Stan wins "); else printf("Ollie wins "); } return 0; }
二:Good Luck in CET-4 Everybody!
题意:有N个石子,每次可以取2的幂次个石子,即1,2,4,8...最后一个取到的胜利。
思路:先手取后变成3的倍数,然后后手无论取什么,都变成不是3的倍数。 直到先手取后变成3,然后无论后手取1还是2,先手胜。

#include<cstdio> #include<cstdlib> #include<iostream> #include<cstring> using namespace std; int main() { int n; while(~scanf("%d",&n)){ if(n%3==0) printf("Cici "); else printf("Kiki "); } return 0; }
题意:两人在N*N的棋盘上走,起始在角落,轮流走,每次可以走到旁边一格,不能走重复的点,不能走动为输。
思路:发现先手每次占据一个奇点位置(如1*1),然后后手只能占据一个偶点位置(如1*2,2*1),所以N为奇时,后手会被逼死。

#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int n,m; int main() { while(~scanf("%d",&n)){ if(n==0) return 0; if(n&1) printf("ailyanlu "); else printf("8600 "); } return 0; }
四:游戏Game(二分图博弈)
题意:给定一个矩阵棋盘,有些地方有阻碍不能走。先手选一点设为起点,然后从后手开始,一人走一步,走不动的输。
思路:https://www.cnblogs.com/jffifa/archive/2011/12/17/2291069.html
五:Moving Pebbles(对称博弈)
题意:任选一堆,首先拿掉至少一个石头,然后移动任意个石子到任意一些堆中. 不能移动的输。n<=100000
思路:利用对称性:注意题目中说的是移动任意石子到任意一些堆中,而非只能移动到一个堆(。。。我开始就这么以为,只要这里没有都错题,
就不难想到后者可以模仿前一位的操作。
【图论 SG】
一:Fibonacci again and again(简单SG)
题意:有3堆石子,每次可以从任意堆中 取出 F个(F是1,2,3,5,8,13...),最后一个取的人胜利。
思路:SG求出来,求异或和即可。

#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> using namespace std; int f[20],SG[1010],ex[20]; void solve() { for(int i=1;i<=1000;i++) { for(int j=1;j<=15;j++) { if(f[j]>i) break; ex[SG[i-f[j]]]=i; } for(int j=0;j<=1000;j++) if(ex[j]!=i){ SG[i]=j; break; } } } int main() { int n,m,p,i,j; f[1]=1;f[2]=2; for(i=3;i<=15;i++) f[i]=f[i-1]+f[i-2]; solve(); while(~scanf("%d%d%d",&n,&m,&p)){ if(n==0&&m==0&&p==0) return 0; if(SG[n]^SG[m]^SG[p]) printf("Fibo "); else printf("Nacci "); } return 0; }
二:取石子游戏 (SG打表找规律)
题意:有N堆石子,每次可以选择在任意一堆取任意多的石子。也可以选择任意一堆,分成两堆。没有可以操作的选手输。
思路:求出每一堆的SG函数,最后求异或和。 对于一堆有X个石子的堆,取后SG=:0,1,2...X-1;可以分:SG[1]^SG[X-1],,SG[2]^SG[X-2]...。 我们打表出前面的SG函数,发现SG函数的规律是以4为周期变化的,具体为i,i+1,i+3,i+2。

#include<cstdio> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; const int maxn=20000; int sg[maxn]; void solve() { for(int i=1;i<=maxn;i+=4){ sg[i]=i; sg[i+1]=i+1; sg[i+2]=i+3; sg[i+3]=i+2; } } int main() { int N,i,x,Xor=0; solve(); scanf("%d",&N); for(i=1;i<=N;i++){ scanf("%d",&x); Xor^=sg[x]; } if(Xor) printf("Alice "); else printf("Bob "); }
三:kiki's game (SG打表找规律)
题意:开始棋子在(n,m),每次可向左,或向上,或左上走一步。走到(1,1)者胜利。
思路:打表出SG图找规律。发现n和m都为奇数点才是必胜N态。

#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int n,m; int main() { while(~scanf("%d%d",&n,&m)){ if(n==0&&m==0) return 0; n%=2; m%=2; if(n==0||m==0) printf("Wonderful! "); else printf("What a pity! "); } return 0; }