决定近段时间复习一下博弈论顺便写点笔记。
大佬博客:几种常见博弈模型https://blog.csdn.net/wr132/article/details/51213331
SG函数与SG定理https://www.cnblogs.com/ECJTUACM-873284962/p/6921829.html
无敌的博弈总结https://blog.csdn.net/acm_cxlove/article/details/7854526
常见博弈模型
首先要清楚博弈论的基础知识概念(必胜必败态,多堆游戏,公平组合游戏,有向图游戏),然后了解几种常见的博弈模型,其中中比较常见的应该就是巴什博弈和尼姆博弈了。把一些题目转换为经典的博弈论模型能够快速地找到规律解决问题。
巴什博弈:只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个,最后取光者得胜。n%(m+1)=0,是必先手败的局势。
威佐夫博奕:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。奇异局势:ak =[k(1+√5)/2],bk= ak + k先手必败。
尼姆博弈:有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。(a,b,c)是必败态等价于a^b^c=0。
HDU-2149
巴什博弈裸题,问是否先手必胜,是的话输出先手第一次出手的必胜策略。m%(n+1)不为0先手必胜,必胜策略分两种如果m<=n那么先手就可以一次胜利,否则就第一次就要出m%(n+1)。
#include<bits/stdc++.h> using namespace std; int main() { int m,n; while (scanf("%d%d",&m,&n)==2) { if (m%(n+1)==0) { puts("none"); continue; } if (m<=n) { printf("%d",m); for (int i=m+1;i<=n;i++) printf(" %d",i); } else { printf("%d",m%(n+1)); } puts(""); } return 0; }
POJ-1067
威佐夫博奕裸题,只有奇异局势的时候是必败态,奇异局势的公式为 ak =[k(1+√5)/2],bk= ak + k 。我们怎么判断呢?用bk-ak得到k,然后用这个k计算得到ak看看和原ak是否相等。
#include<iostream> #include<cstdio> #include<cmath> using namespace std; int main() { int n,m; while (scanf("%d%d",&n,&m)==2) { if (m>n) swap(n,m); int k=n-m; int tmp=floor(k*(1.0+sqrt(5.0))/2); if (tmp==m) puts("0"); else puts("1"); } return 0; }
HDU-1847
巴什博弈变形。我们观察到3是一个先手必败点,所以大家都避免走到3,但是只要有一个人避免了必败点他就可以通过对方出的数凑成3的倍数让对方必败。所以n是3的倍数时候先手必败否则先手必胜。
#include<bits/stdc++.h> using namespace std; int main() { int n; while (scanf("%d",&n)==1) { if (n%3) puts("Kiki"); else puts("Cici"); } return 0; }
SG函数与SG定理
但是对于一些非常见博弈模型或者是模型变形规律不明显的,我们有一个大杀器:SG函数。通过SG函数我们能够快速直到某个点是必胜/必败态从而找到规律。
做了HDU-1848当作求SG函数的模板:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1e3+10; 4 int f[30],S[N],SG[N]; 5 6 void getSG(int n) { 7 memset(SG,0,sizeof(SG)); 8 for (int i=1;i<=n;i++) { 9 memset(S,0,sizeof(S)); //后继SG状态函数集合 10 for (int j=0;j<=20&&f[j]<=i;j++) S[SG[i-f[j]]]=1; //标记后继状态SG函数 11 for (int j=0;;j++) 12 if (!S[j]) { //mex:后继SG第一个没出现的 13 SG[i]=j; break; 14 } 15 } 16 } 17 18 int main() 19 { 20 int n,m,k; 21 f[0]=f[1]=1; 22 for (int i=2;i<=20;i++) f[i]=f[i-1]+f[i-2]; //求出可操作集 23 getSG(1000); //计算前1000的SG函数 24 while (scanf("%d%d%d",&n,&m,&k) && (n||m||k)) { 25 if (SG[n]^SG[m]^SG[k]) printf("Fibo "); //多堆游戏XOR和 26 else printf("Nacci "); 27 } 28 return 0; 29 }
HDU-1536
SG函数直接应用。要注意这题输入的操作集合不是升序的要先排序才行。
#include<bits/stdc++.h> using namespace std; const int N=1e4+10; int k,n,m,f[110],S[N],SG[N]; void getSG(int n) { memset(SG,0,sizeof(SG)); memset(S,-1,sizeof(S)); for (int i=1;i<=n;i++) { for (int j=1;j<=k&&f[j]<=i;j++) S[SG[i-f[j]]]=i; for (int j=0;;j++) if (S[j]!=i) { SG[i]=j; break; } } } int main() { while (scanf("%d",&k) && k) { for (int i=1;i<=k;i++) scanf("%d",&f[i]); sort(f+1,f+k+1); getSG(10000); scanf("%d",&n); for (int i=1;i<=n;i++) { int m,tmp=0; scanf("%d",&m); for (int j=1;j<=m;j++) { int x; scanf("%d",&x); tmp^=SG[x]; } if (tmp==0) printf("L"); else printf("W"); } puts(""); } return 0; }
HDU-3980
这道题会难一些。首先原来是一个环不好处理,但是我们注意到只要第一步下手后就会变成链,所以我们一开始从n-m这条链开始求SG函数。那么假设此时是一条长度为n的链,我们在当前选手在上面选择了长度为m的区间染色,之后这条链就会分割成两段分别是i和n-i-m的后继状态,于是此时就会变成两个游戏,然后根据多堆Nim博弈异或和的结论,后继状态的SG函数就是SG[i]^SG[n-i-m],所以我们就能标记所有后继的SG函数得到当前状态的SG函数。
#include<bits/stdc++.h> using namespace std; const int N=1e3+10; int n,m,SG[N],S[N]; int getSG(int n) { if (n<m) return 0; if (SG[n]!=-1) return SG[n]; for (int i=0;n-i-m>=0;i++) S[getSG(i)^getSG(n-i-m)]=n; //Nim博弈 for (int i=0;;i++) if (S[i]!=n) { SG[n]=i; break; } return SG[n]; } int main() { int T,Case=0; cin>>T; while (T--) { scanf("%d%d",&n,&m); memset(SG,-1,sizeof(SG)); memset(S,-1,sizeof(-1)); SG[0]=0; if (n<m || getSG(n-m)) printf("Case #%d: abcdxyzk ",++Case); else printf("Case #%d: aekdycoin ",++Case); } return 0; }
POJ-2505
SG函数应该很容易求。但是此题的数字非常大数组决定存不下,可以考虑用map计算SG函数可以获得AC。
#include<cstdio> #include<iostream> #include<map> using namespace std; typedef long long LL; LL n; map<LL,LL> SG; LL getSG(LL x) { if (x>=n) return 0; if (SG.count(x)) return SG[x]; map<LL,int> S; for (int i=2;i<=9;i++) S[getSG(x*i)]=1; for (int i=0;;i++) if (!S.count(i)) return SG[x]=i; } int main() { while (scanf("%lld",&n)==1) { SG.clear(); if (getSG(1)) printf("Stan wins. "); else printf("Ollie wins. "); } return 0; }
其他题目练习:
POJ-2484
很经典的一道题。给n个连成环的硬币,每次每人可以取一个或者相邻的两个硬币,最后取不到硬币的人输掉。这题要运用一种平均分然后不断模仿对方决策得到必胜的思想。首先如果硬币个数<=2先手必胜,大于3的时候第一个人取完之后环会变成一条链,然后无论第一个人取的是一个还是两个第二个人都可以根据第一个人取的数量和位置做出“把剩下硬币取成完全相同的两条链”(这里要重点理解)。然后通过模仿对方达到不败之地。所以硬币数量>=3时候先手必败。
#include<iostream> #include<cstdio> using namespace std; const int N=1e6+10; int n,m; int main() { while (scanf("%d",&n)==1 && n) { if (n<=2) puts("Alice"); else puts("Bob"); } return 0; }
POJ-2425
图上的博弈:一个n个点的有向无环图,有某些点有硬币,每次没人只可以把一个棋子往前推一步(当然是沿着边的方向推),最后不能推的人输掉。尽管是在DAG上的博弈,但是SG函数的求法还是那样,标记所有后继状态之后mex。然后有几个棋子就是XOR和即可。
#include<iostream> #include<cstdio> #include<vector> #include<cstring> using namespace std; const int N=1e3+10; int n,m,q,SG[N]; vector<int> G[N]; int getSG(int x) { if (SG[x]!=-1) return SG[x]; int S[N]={0}; for (int i=0;i<G[x].size();i++) { int y=G[x][i]; S[getSG(y)]=1; } for (int i=0;;i++) if (S[i]!=1) return SG[x]=i; } int main() { while (scanf("%d",&n)==1) { for (int i=1;i<=n;i++) { G[i].clear(); int t; scanf("%d",&t); for (int j=1;j<=t;j++) { int x; scanf("%d",&x); x++; G[i].push_back(x); } } memset(SG,-1,sizeof(SG)); while (scanf("%d",&q) && q) { int ans=0; for (int j=1;j<=q;j++) { int x; scanf("%d",&x); x++; ans^=getSG(x); } printf("%s ",ans?"WIN":"LOSE"); } } return 0; }
POJ 1704 Georgia and Bob
一列格子上有n个棋子,每次没人可以选择一个棋子往左移动若干步,棋子不能越过1格子,棋子也不能越过其他棋子移动,不能移动的人输掉。这题真的挺巧妙的,听大佬说这是一种叫阶梯博弈的博弈模型。我们先考虑对于一组相邻的棋子,这种情况肯定是先手必败,因为先手移动x格同时后手可以用后面那个棋子移动相同格。那么我们把棋子两两配对,那么两两配对的时候前面棋子的移动其实是没有意义的,因为与其配对的后面棋子可以做一模一样的操作。所以唯一的变数变成了两两配对的一组棋子间的间隙。
所以解法就是把棋子两两匹配,奇数时候第一个棋子和格子1匹配,然后对于每一组格子间的间隙当成一堆Nim游戏,求出所有Nim游戏的和就是答案。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; const int N=1e3+10; int n,a[N]; int main() { int T; cin>>T; while (T--) { scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&a[i]); sort(a+1,a+n+1); int ans=0; for (int i=n;i>0;i-=2) ans^=a[i]-a[i-1]-1; printf("%s ",ans?"Georgia will win":"Bob will win"); } return 0; }
POJ 1740 A New Stone Game
POJ 2068 Nim
POJ 3480 John
POJ 2348 Euclid's Game
HOJ 2645 WNim
POJ 3710 Christmas Game
POJ 3533 Light Switching Game
HOJ 4388 Stone Game II
ZJU 3057 beans game