最近看了一下《算法竞赛入门经典——训练指南》中的博弈部分,便急切的做了几道HDU上的博弈水题。
大喊一声:我以后会继续更新的(希望如此)。
两条规则:
规则1:一个状态是必败状态当且仅当它的所有后继都是必胜状态。
规则2:一个状态时必胜状态当且仅当它至少有一个后继是必败状态。
HDU2188 悼念512汶川大地震遇难同胞——选拔志愿者(水)
分析:
这种类型的题目叫做巴什博奕(Bash Game),可以直接做.
AC代码如下:
#include<stdio.h> int main() { int T,n,m; while(scanf("%d",&T)!=EOF) while(T--) { scanf("%d%d",&n,&m); if(n%(m+1)!=0) printf("Grass\n"); else printf("Rabbit\n"); } return 0; }
或者按着规则1、规则2推。
AC代码如下:
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> using namespace std; const int maxn = 10000+10; bool wining[maxn]; int main(){ int T, n, m; scanf("%d", &T); while(T--) { scanf("%d%d", &n, &m); for(int i=0; i<=m; i++) wining[n-i] = true; for(int i=n-m-1; i>=0; i--) { wining[i] = false; for(int j=1; j<=m; j++) { if(i+j <= n && !wining[i+j]) { wining[i] = true; break; } } } if(wining[0]) printf("Grass\n"); else printf("Rabbit\n"); } return 0; }
分析:
和上一题类似。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> using namespace std; int main() { int n, m; while(scanf("%d%d", &n, &m) != EOF) { if(n % (m+1) == 0) printf("none\n"); else { if(n <= m) { for(int i=n; i<=m; i++) { if(i != m) printf("%d ", i); else printf("%d\n", i); } } else { int t = n % (m+1); printf("%d\n", t); } } } return 0; }
HDU1847 Good Luck in CET-4 Everybody!(水)
分析:
也没啥难度,直接推。
AC代码如下:
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> using namespace std; const int maxn = 1000 + 10; int wining[maxn]; bool vis[maxn]; int main() { int n; while(scanf("%d", &n) == 1) { wining[0] = 0; for(int i = 1; i <= n; i++) { wining[i] = false; for(int j = 1; j <= i; j <<= 1) if(!wining[i-j]) { wining[i] = true; break; } } if(wining[n]) printf("Kiki\n"); else printf("Cici\n"); } return 0; }
或者是SG函数,不过感觉多此一举了,就当练习吧。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> using namespace std; const int maxn = 1000 + 10; int sg[maxn]; bool vis[maxn]; int main() { int n; while(scanf("%d", &n) == 1) { sg[0] = 0; for(int i = 1; i <= n; i++) { memset(vis, false, sizeof(vis)); for(int j = 1; j <= i; j <<= 1) vis[sg[i-j]] = true; for(int j = 0; ; j++) if(!vis[j]) { sg[i] = j; break; } } if(sg[n]) printf("Kiki\n"); else printf("Cici\n"); } return 0; }
分析:
本题就是一个 Nim 游戏, 将每个棋子看成一堆火柴, 例如如果 k = 3, 就想象成 一堆火柴含有3根, 每次可以任选一堆中移动不大于 k根。不能移动者输。
利用 Bouton 定理,求 Nim和。
AC代码如下:
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> using namespace std; int main() { int n; while(scanf("%d", &n) == 1 && n) { int ans = 0, k; for(int i = 0; i < n; i++) { scanf("%d", &k); ans ^= k; } if(ans == 0) printf("Grass Win!\n"); else printf("Rabbit Win!\n"); } return 0; }
HDU1850 Being a Good Boy in Spring Festival
分析:
Nim 游戏。 利用 Bouton 定理。
难点在于如何求第一步可行的方案数。
设a[1] ^ a[2] ^ a[3] ^ ....^ a[n] = ans (ans != 0) (1)
那么可行的方案就是选择 1 ~ n 中一堆,减少牌的数量, 使得改变后的 a[1] ^ a[2] ^ a[3] ^ .... ^ a[n] = ans = 0 (2)
假设要改变第 j 堆,设改变后的数量为 a[j]', 使得 a[1] ^ a[2] ^ a[3] ^ ... ^ a[j]' ^ .... ^ a[n] = 0 (3)
设 t = a[1] ^ a[2] ^....^ a[j-1] ^ a[j+2] ^ .... ^ a[n] (4)
由异或的性质可知 t = ans ^ a[j] (5)
如何使式子 (3) = 0 呢? 只要使 a[j]' = t 就可以了。因为 ans = a[j]' ^ t = 0.
需要注意的是,只能减少牌的数量,所以 符合要求的 t 要小于 a[j](不能等于哦)。
以上是菜鸟的个人见解,并不敢保证思路严谨。莫喷。
AC代码如下:
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> using namespace std; const int maxn = 100+10; int a[maxn]; int main() { int n; while(scanf("%d", &n) == 1 && n) { int ans = 0; for(int i = 0; i < n; i++) { scanf("%d", &a[i]); ans ^= a[i]; } if(ans == 0) printf("0\n"); else { int cnt = 0; for(int i = 0; i < n; i++) { int t = a[i] ^ ans; if(t < a[i]) cnt++; } printf("%d\n", cnt); } } return 0; }
网上找的博弈的资料:http://www.wutianqi.com/?p=1081
上面的资料没有对S1,S2,T1,T2等进行定义,个人联系上下文,猜测S1定义为,在S态中充裕堆的维数为1,S2为充裕堆的维数为2. T1,T2同理。
HDU1907 John (水)
分析:
对应于上面资料中的取火柴游戏中的题目2.
AC代码如下:
#include <cstdio> #include <cstring> #include <iostream> using namespace std; int main() { int T, n, a; scanf("%d", &T); while(T--) { int ans = 0, cnt = 0;//cnt判断充裕堆的堆数等于0; scanf("%d", &n); for(int i=0; i<n; i++) { scanf("%d", &a); ans ^= a; if(a > 1) cnt++; } if(cnt) { if(ans) printf("John\n"); else printf("Brother\n"); } else { if(n % 2) printf("Brother\n"); else printf("John\n"); } } return 0; }
分析:
和上一题一样。
#include <cstdio> #include <cstring> #include <iostream> using namespace std; int main() { int n, a; while(scanf("%d", &n) == 1) { int ans = 0, cnt = 0;//cnt判断充裕堆的堆数等于0; for(int i=0; i<n; i++) { scanf("%d", &a); ans ^= a; if(a > 1) cnt++; } if(ans) { if(cnt) printf("Yes\n"); else printf("No\n"); } else { if(cnt) printf("No\n"); else printf("Yes\n"); } } return 0; }
HDU1944 S-Nim(不错的题)
详见本博客的一篇题解:http://www.cnblogs.com/tanhehe/archive/2013/05/31/3111181.html
HDU1517 A Multiplication Game (不错的题)
详见本博客的一篇题解:http://www.cnblogs.com/tanhehe/archive/2013/05/31/3111136.html
Simple Game(第二届山东省赛题)
分析:
做的时候是这样想的,本题可以从1~3堆中取,假设对手取2次,那么自己也取2次,再把过程变一下,想象成,对手取一次,自己取一次,对手取一次,自己取一次。取3次的时候也一样。这样就完全是 Nim 博弈了。
#include <iostream> #include <cstdio> using namespace std; int main() { int T, n, x; cin >> T; while(T--) { cin >> n; int ans = 0; for(int i=0; i<n; i++) { cin >> x; ans ^= x; } if(n <= 3 || ans) cout << "Yes\n"; else cout << "No\n"; } }