博弈论
推荐博客:
常见的几种组合博弈:http://www.cnblogs.com/ECJTUACM-873284962/p/6398385.html
SG函数与SG定理:https://www.cnblogs.com/ECJTUACM-873284962/p/6921829.html
SG函数:https://blog.csdn.net/kamisama123/article/details/77649118
例题:
1.hdoj1846 Brave Game
传送:http://acm.hdu.edu.cn/showproblem.php?pid=1846
题意:一堆石子,两个人轮流取,要求每次取的个数在$(1-m)$内。问谁获胜。
分析:巴什博弈模板。
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 int main(){ 4 int t,n,m; scanf("%d",&t); 5 while (t--){ 6 scanf("%d%d",&n,&m); 7 if (n%(m+1)==0) printf("second\n"); else printf("first\n"); 8 } 9 return 0; 10 }
2.hdoj2149 Public Sale
传送:http://acm.hdu.edu.cn/showproblem.php?pid=2149
题意:物品初始价值为0,两个人轮流加价,范围为$1-m$,当价值$\ge n$时,获胜。问先手第一次加价多少时,一定获胜。
分析:巴什博弈。保证先手必胜的状态下输出先手应该选择的数值。注意$n \le m$。
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 int main(){ 4 int n,m; 5 while (~scanf("%d%d",&n,&m)){ 6 if (m>=n){ 7 for (int i=n;i<m;i++) printf("%d ",i); 8 printf("%d\n",m); 9 continue; 10 } 11 if (n%(m+1)==0){ 12 printf("none\n"); 13 continue; 14 } 15 printf("%d\n",n%(m+1)); 16 } 17 return 0; 18 }
3.hdoj2188 选拔志愿者
传送:http://acm.hdu.edu.cn/showproblem.php?pid=2188
题意:物品初始价值为0,两个人轮流加价,范围为$1-m$,当价值$\ge n$时,获胜。问谁获胜。
分析:巴什博弈。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int main(){ 4 int t,n,m; cin >> t; 5 while (t--){ 6 cin >> n >> m; 7 if (n%(m+1)==0) cout << "Rabbit\n"; 8 else cout << "Grass\n"; 9 } 10 return 0; 11 }
4.hdoj4767 Stone
传送:http://acm.hdu.edu.cn/showproblem.php?pid=4764
题意:两个人轮流写数,要求后一个数比前一个数大,且范围在$1-m$,第一个写下$\geq n$的人输。
分析:巴什博弈。
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 int main(){ 4 int n,m; 5 while (cin >> n >> m){ 6 if (n+m==0) break; 7 if ((n-1)%(m+1)==0) cout << "Jiang\n"; 8 else cout << "Tang\n"; 9 } 10 return 0; 11 }
5.hdoj 2147 kiki's game
传送:http://acm.hdu.edu.cn/showproblem.php?pid=2147
题意:有一个棋盘,两个人轮流操作,每次可以走左,下,左下三个方向,不可操作的人输。问先手的输赢。
分析:用必败,必胜态分析。
代码:
6.hdoj2516 取石子游戏
传送:http://acm.hdu.edu.cn/showproblem.php?pid=2516
题意:有一堆石子,两人轮流取,先手第1次可以取任意多个,但不能全部取完。以后每次取的石子数不能超过上次取子数的2倍。取完的人获胜。
分析:斐波那契博弈。https://blog.csdn.net/acm_cxlove/article/details/7835016
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 ll fib[50]; 5 int main(){ 6 fib[1]=1; fib[2]=2; 7 for (int i=3;i<50;i++) fib[i]=fib[i-1]+fib[i-2]; 8 ll n; 9 while (cin >> n && n){ 10 int f=1; 11 for (int i=1;i<50;i++) 12 if (fib[i]==n){ 13 printf("Second win\n"); 14 f=0; 15 } 16 if (f) printf("First win\n"); 17 } 18 return 0; 19 }
7.hdoj1527 取石子游戏
传送:http://acm.hdu.edu.cn/showproblem.php?pid=1527
题意:有两堆石子,两个人轮流取,每次可以在一堆中取任意个,也可以在两堆中取同样多个。取完为胜,问先手的输赢。
分析:威佐夫博弈模板。
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 int main(){ 4 int a,b; 5 while (~scanf("%d%d",&a,&b)){ 6 if (a>b) swap(a,b); 7 double k=(sqrt(5.0)-1.0)/2.0; 8 int j=a*k; 9 if (a!=j*(int)(k+1)) j++; 10 if (a+j==b) cout << "0\n"; else cout << "1\n"; 11 } 12 }
8.hdoj2177 取(2堆)石子游戏
传送:http://acm.hdu.edu.cn/showproblem.php?pid=2177
题意:有两堆石子,两个人轮流取,每次可以在一堆中取任意个,也可以在两堆中取同样多个。取完为胜,问若先手获胜第一次需要取多少。
分析:威佐夫博弈。输出第一次的方案,性质:对于每一个自然数都属于一个奇异局势。一对自然数的差值对应一个奇异局势。
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 int main(){ 4 int n,m; double tmp=(sqrt(5)+1)/2.0; 5 while (~scanf("%d%d",&n,&m)){ 6 if (n+m==0) break; 7 if (n>m) swap(n,m); 8 int b=(m-n)*tmp; 9 if (n==b) printf("0\n"); 10 else{ 11 printf("1\n"); 12 if (b<=n) printf("%d %d\n",b,b+m-n); 13 if (n==0 || m==0) printf("0 0\n"); 14 for (int i=1;i<=m;i++){ 15 b=i*tmp; 16 if (b==tmp) continue; 17 if (b==n && b+i<=m) printf("%d %d\n",b,b+i); 18 else if (b+i==n && b<=m) printf("%d %d\n",b,b+i); 19 else if (b<=n && b+i==m) printf("%d %d\n",b,b+i); 20 else if (b+i<=n && b==m) printf("%d %d\n",b,b+i); 21 } 22 } 23 } 24 return 0; 25 }
9.hdoj1850 Being a Good Boy in Spring Festival
传送:http://acm.hdu.edu.cn/showproblem.php?pid=1850
题意:n堆石子,每次可以取一堆中的任意个,问先手获胜的前提下第一次取的方案数有多少。
分析:nim博弈。
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 int a[150]; 4 int main(){ 5 int n; 6 while (~scanf("%d",&n)){ 7 if (n==0) break; 8 int flag=0; 9 for (int i=0;i<n;i++){ 10 cin >> a[i]; 11 flag^=a[i]; 12 } 13 int ans=0; 14 for (int i=0;i<n;i++){ 15 int tmp=flag^a[i]; 16 if (tmp<a[i]) ans++; 17 } 18 printf("%d\n",ans); 19 } 20 return 0; 21 }
10.hdoj1847 Good Luck in CET-4 Everybody!
传送:http://acm.hdu.edu.cn/showproblem.php?pid=1847
题意:一堆石子,两个人轮流取,每次可以取2的幂次方个。问先手是否可以获胜。
分析:SG打表。
代码:
1 #include<iostream> 2 using namespace std; 3 int main() { 4 int n; 5 while(scanf("%d",&n)==1) { 6 if(n%3==0) 7 cout<<"Cici"<<endl; 8 else 9 cout<<"Kiki"<<endl; 10 } 11 return 0; 12 }
11.hdoj1848 Fibonacci again and again
传送:http://acm.hdu.edu.cn/showproblem.php?pid=1848
题意:有三堆石子,两个人轮流取,每次要求取的个数为斐波那契值。问谁会赢。
分析:三堆石子的nim游戏。用SG函数处理,最后取异或和即好。
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=1010; int f[maxn],sg[maxn],S[maxn]; void init(){ f[1]=1; f[2]=2; int tot=2; for (int i=3;i;i++){ tot++; f[i]=f[i-1]+f[i-2]; if (f[i]>maxn) break; } //SG memset(sg,0,sizeof(sg)); for (int i=1;i<maxn;i++){ memset(S,0,sizeof(S)); //标记后继状态 for (int j=1;f[j]<=i && j<tot;j++) S[sg[i-f[j]]]=1; for (int j=0;;j++) if (!S[j]){sg[i]=j;break;} } //for (int i=0;i<=20;i++) cout << sg[i] << endl; } int main(){ int n,m,k; init(); while (~scanf("%d%d%d",&n,&m,&k)){ if (n+m+k==0) break; if (sg[n]^sg[m]^sg[k]) printf("Fibo\n"); else printf("Nacci\n"); } return 0; }