SG 函数 博弈问题
前言:
如果你没有了解过博弈问题,你可以先点击 这里 学习一下,讲的非常好,我只能orz,这里我讲出我的理解。
我的理解:
定义N为先手必胜,P为先手必败。
icg博弈可以转换成有向无环图
游戏开始时有一颗放在起点的棋子,
两个玩家轮流移动棋子,
直到不能移动的玩家落败。
所有只能移动到终点的局面都是必胜的,
就是N下面至少有一个P
而P下面全部都是N
即所有只能连接必胜点的点是必败的。
我们用递归的思路可以计算出所有点的状态。
因为遍历搜素所有状态太耗时了,于是出现了sg函数(魔法一般的函数)
定义p点是零级胜态,和 p直接相连的点是一级胜态点,可以进入一级胜态点和零级胜态点的点是二级胜态点。以此类推,n级胜态点和n-1级胜态点都相连。
两个同级的胜态组合是败态,两个不同级的胜态组合是胜态,因为后手可以拷贝先手的操作,直到无法继续操作,游戏结束,所以,两个同级的胜态组合是败态。而两个不同的胜态,先手可以将其中一个转化成和另一个一样,从而让后手面临两个一样的胜态,所以不同的胜态组合是胜态。
SG函数能够用来将一个状态映射成一个非负整数的值
SG(A)=mex(SG(B)|A->B)
也就是说,SG函数的值就是表示当前状态是几级胜态,比如SG(A)=1,那么A就是一级胜态
因为两个同级的胜态组合是败态,两个不同级的胜态组合是胜态,所以SG函数异或之后如果为零,那么就是必败,否则就是必胜
总结 就是他定义了胜态的概念,然后牢记同级胜态组合必败,不同级胜态组合必胜,
最后值得注意的是 多组石头的胜态是每组石头的胜态的异或和。 其实就是上面的原理。
相应题目
ac代码:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> using namespace std; #define ll long long #define INF 0x3f3f3f3f const int N=20; int f[N],sg[N],s[N]; int gcd(int a,int b){ if(b==0)return a; return gcd(b,a%b); } void getf(int k){ for(int i=1;i<=k;i++){ if(gcd(i,k)==1)f[i]=1; else f[i]=0; } } void getSG(int n){ int i,j; memset(sg,0,sizeof(sg)); for(i=1;i<=n;i++){//求对于i的sg memset(s,0,sizeof(s)); getf(i); for(j=1;j<=i;j++){ if(f[j]){ s[sg[i-j]]=1; } } for( j=1;j<=i/2;j++){ s[sg[j]^sg[i-j]]=1; } for(j=0;;j++)if(s[j]!=1){ sg[i]=j; break; } } } int main(){ getSG(15); int t; scanf("%d",&t); while(t--){ int n; scanf("%d",&n); if(sg[n]!=0){ printf("Teacher Ma\n"); } else{ printf("Xiao Huo Zi\n"); } } }