学长的博弈总结博客 orz
博弈SG函数模板
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define maxn 1005 using namespace std; int f[maxn],sg[maxn],mex[maxn]; void get_f(int n) { int i; f[1]=1;f[2]=2; for (i=3;f[i-1]<n;i++) f[i]=f[i-1]+f[i-2]; } void get_sg(int n) { int i,j; memset(sg,0,sizeof(sg)); for (i=1;i<=n;i++) { memset(mex,0,sizeof(mex)); for (j=1;f[j]<=i;j++) mex[sg[i-f[j]]]=1; for (j=0;j<=n;j++) if (!mex[j]) break; sg[i]=j; } } int main() { int m,n,p; get_f(1000); get_sg(1000); while (scanf("%d%d%d",&m,&n,&p)!=EOF && m!=0) { if (sg[m]^sg[n]^sg[p]) printf("Fibo "); else printf("Nacci "); } return 0; }
C HDU 1404
因为输入的字符串只有6位,就可以把它当作一个6位整数x。
从(先手)必败状态x开始,可进行的操作有①在x的某一位上加上一个数(加上后该位不超过9)②当x不足6位,可以在x后面加0(即乘以10或100...)
这样由必败状态推出的所有状态均为必胜状态。
预处理1000000以内的所有数的胜败状态,每次输入时根据对应值输出即可。
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define maxn 1000005 using namespace std; int sg[maxn]; char s[12]; void get_sg(int x) { int i,j,y=x,l=0,now,k; while (y) { l++;y=y/10; } for (i=1,k=1;i<=l;i++,k=k*10) { now=x/k%10; y=x; for (j=now;j<9;j++) { y+=k; sg[y]=1; } } y=x; for (k=1;l<6;l++,k=k*10) { y=y*10; for (i=0;i<k;i++) sg[y+i]=1; } } int main() { int i,n,m,len,x; sg[0]=1; for (i=1;i<1000000;i++) if (!sg[i]) get_sg(i); while (scanf("%s",s)!=EOF) { if (s[0]=='0') printf("Yes "); else { x=0; len=strlen(s); for (i=0;i<len;i++) x=x*10+s[i]-'0'; if (sg[x]) printf("Yes "); else printf("No "); } } return 0; }
E HDU 3980
第一个人先涂m个,把n长的链变为n-m长的环,每次的结果等于涂色后分出的两端的答案的异或和。据此就可递归求出sg了。
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define maxn 1005 using namespace std; int sg[maxn],mex[maxn],n,m; int findsg(int x) { if (sg[x]!=-1) return sg[x]; if (x<m) return sg[x]=0; int i; memset(mex,0,sizeof(mex)); for (i=m;i<=x;i++) mex[findsg(i-m)^findsg(x-i)]=1; for (i=0;;i++) if (!mex[i]) break; return sg[x]=i; } int main() { int T,t,i; scanf("%d",&T); for (t=1;t<=T;t++) { scanf("%d%d",&n,&m); printf("Case #%d: ",t); if (n<m) printf("abcdxyzk "); else { n=n-m; memset(sg,-1,sizeof(sg)); for (i=0;i<=n;i++) sg[i]=findsg(i); if (sg[n]) printf("abcdxyzk "); else printf("aekdycoin "); } } return 0; }
设这n堆石子中,石子数量最少为x,cnt数组记录不同石子数量在n堆石子中出现的次数。
则若cnt[x]<=n/2,此时必胜;若cnt[x]>n/2,此时必败,因为cnt[x]<=n/2可以通过取石子的方式转移至该状态。
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define maxn 55 #define inf 99999999 using namespace std; int cnt[maxn]; int main() { int i,x,n,re=inf; scanf("%d",&n); for (i=1;i<=n;i++) { scanf("%d",&x); cnt[x]++; re=min(re,x); } if (cnt[re]<=n/2) printf("Alice "); else printf("Bob"); return 0; }
H POJ 1704
I POJ 2425
题意:给一个图,图中有m条有向边,给出几个棋子的初始位置,游戏双方轮流沿着边移动这几个棋子,最后无法移动这几个棋子的人输。
思路:把每个棋子看作一个子游戏,通过记忆化搜索求出sg数组后,将每个棋子的答案异或就是最终答案。
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define maxn 1005 using namespace std; int sg[maxn],mp[maxn][maxn],n,ans; int dfs(int x) { if (sg[x]!=-1) return sg[x]; int mex[maxn]; memset(mex,0,sizeof(mex)); int i; for (i=0;i<n;i++) if (mp[x][i]) mex[dfs(i)]=1; for (i=0;;i++) if (!mex[i]) break; return sg[x]=i; } int main() { int m,x,i; while (scanf("%d",&n)!=EOF) { memset(mp,0,sizeof(mp)); memset(sg,-1,sizeof(sg)); for (i=0;i<n;i++) { scanf("%d",&m); if (m==0) sg[i]=0; while (m--) { scanf("%d",&x); mp[i][x]=1; } } for (i=0;i<n;i++) { sg[i]=dfs(i); } while (scanf("%d",&m)!=EOF && m!=0) { ans=0; while (m--) { scanf("%d",&x); ans=ans^sg[x]; } if (ans) printf("WIN "); else printf("LOSE "); } } return 0; }
(补充一个网上找到讲博弈论基础的博客https://www.cnblogs.com/lfri/p/10662291.html