NIM游戏
根据游戏规则无法再进行行动者为负。
就是将各个子游戏的sg值进行异或,不为零则初始为N态(先手必胜)。比如抓石子游戏,sg[i]=i。
S-NIM游戏
取走最后一个石子者为负。(终止态为N态)
先手必胜当且仅当:
1.所有堆的石子数都为1,且游戏的SG值为0(此时退化为简单的奇偶处理)
2.存在石子数大于1的堆,且游戏的SG值不为0
入门例题:LightOJ 1253 Misere NIM
http://lightoj.com/volume_showproblem.php?problem=1253
#include <iostream>
#include <cstdio>
using namespace std;
int a[105];
int main()
{
int t;
scanf("%d",&t);
for(int tt=1;tt<=t;tt++)
{
int ans=0;
int flag=0;
int k;
scanf("%d",&k);
for(int i=0;i<k;i++)
{
scanf("%d",&a[i]);
if(a[i]>1)
flag=1;
}
for(int i=0;i<k;i++)
ans^=a[i];
if((ans&&flag)||(!flag&&!ans))
printf("Case %d: Alice\n",tt);
else
printf("Case %d: Bob\n",tt);
}
return 0;
}
SG函数
关于SG函数,大佬的文章讲的非常清楚
https://blog.csdn.net/strangedbly/article/details/51137432
关于SG函数的入门题目:
LightOJ 1296 Again Stone Game
http://lightoj.com/volume_showproblem.php?problem=1296
还是取石子游戏,只是加上一个限制,每次最多不能超过该堆石子总数的一半(向下取整)
所以显然,石子数为1时是一个P态(无法取了)。数量=2时只能取一个,他的后继状态就是数量=1,所以SG[2]=mex(SG[1])=1。同理,SG[3]=mex(SG[2])=0。
因为数据量很大(1e9)没法打表,所以找下规律。
多推几个就会发现,数量i是偶数时,SG[i]=i/2;数量i是奇数时,SG[i]=SG[i/2]。
代码:
#include <iostream>
#include <cstdio>
using namespace std;
int sg(int x)
{
if(x==1||x==0)
return 0;
if(x%2==0)
return x/2;
else
return sg(x/2);
}
int main()
{
int t;
scanf("%d",&t);
for(int tt=1;tt<=t;tt++)
{
int k,ans=0;
scanf("%d",&k);
for(int i=0;i<k;i++)
{
int ai;
scanf("%d",&ai);
ans^=sg(ai);
}
if(ans)
printf("Case %d: Alice\n",tt);
else
printf("Case %d: Bob\n",tt);
}
return 0;
}
LightOJ 1315 Game of Hyper Knights
http://lightoj.com/volume_showproblem.php?problem=1315
可以说是SG打表的模板题了。
左上角的点肯定是P态,从左上角开始打表,每个点的SG值就是mex{6个后继状态}。
因为计算当前点时会用到后继点的SG,所以打表时需要斜着打。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#define N 505
using namespace std;
int sg[N][N];
int Hash[N];
void getsg()
{
for(int i=0;i<N*2;i++)
for(int j=0;j<=i/2;j++)
{
if(i-j<N)
{
memset(Hash,0,sizeof(Hash));
if(j-2>=0&&i-j-1>=0)
Hash[sg[j-2][i-j-1]]=1;
if(j-3>=0&&i-j-1>=0)
Hash[sg[j-3][i-j-1]]=1;
if(j-1>=0&&i-j-2>=0)
Hash[sg[j-1][i-j-2]]=1;
if(j-1>=0&&i-j-3>=0)
Hash[sg[j-1][i-j-3]]=1;
if(j+1<N&&i-j-2>=0)
Hash[sg[j+1][i-j-2]]=1;
if(j-2>=0&&i-j+1<N)
Hash[sg[j-2][i-j+1]]=1;
for(int k=0;k<N;k++)
if(!Hash[k])
{
sg[j][i-j]=sg[i-j][j]=k;
break;
}
}
}
}
int main()
{
getsg();
int t;
scanf("%d",&t);
for(int tt=1;tt<=t;tt++)
{
int n;
scanf("%d",&n);
int ans=0;
for(int i=0;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
ans^=sg[a][b];
}
if(ans)
printf("Case %d: Alice\n",tt);
else
printf("Case %d: Bob\n",tt);
}
return 0;
}
lightoj 1199 Partitioning Game
http://lightoj.com/volume_showproblem.php?problem=1199
有n堆石子,每次选一堆将其分成数量不等的两堆。不能分的人输。
显然,1和2是P态,SG=0。
3可以分成1和2,然后对手无法分割,是N态;4只能分成1和3,然后对手把3分成1和2,自己无法分割,所以4是P态。这两个比较好推,因为3和4都只有一个后继状态。
对于5,可以分成1+4和2+3,根据SG值的定义,
应该是求mex{SG[后继a(1+4)] , SG[后继b(2+3)]}。
对于1+4的SG值的求法,应该是SG[1] ^ SG[4]。因为将1+4这个状态单拿出来看成一个新游戏,这个子游戏的SG值就是SG[1] ^ SG[4],而SG[1]和SG[4]都已经求出,所以可以这样通过前面的SG值一步步推出全部的SG值。
所以SG[i]=mex{SG[1] ^ SG[i-1] , SG[2] ^ SG[i-2] , … , SG[j] ^ SG[i-j]} (1<=j<(i+1)/2)
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#define N 10005
using namespace std;
int sg[N],Hash[N];
void getsg()
{
for(int i=3;i<N;i++)
{
memset(Hash,0,sizeof(Hash));
for(int j=1;j<(i+1)/2;j++)
Hash[sg[j]^sg[i-j]]=1;
for(int j=0;j<N;j++)
if(!Hash[j])
{
sg[i]=j;
break;
}
}
}
int main()
{
getsg();
int t;
scanf("%d",&t);
for(int tt=1;tt<=t;tt++)
{
int ans=0;
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
int ai;
scanf("%d",&ai);
ans^=sg[ai];
}
if(ans)
printf("Case %d: Alice\n",tt);
else
printf("Case %d: Bob\n",tt);
}
return 0;
}