题目
有n组石子,每组石子有两堆石子,
两人轮流对每个没有结束的游戏进行操作,
每次可以从多的那一堆里减去少的的若干倍
(当然不能超过多的这一堆的上界)
两堆里至少有一堆为0就视为该子游戏结束
给定局面,问谁必胜,
胜当且仅当最后结束的一局胜,
即对手此时无法操作
思路来源
https://www.cnblogs.com/cjyyb/p/9495063.html
https://blog.csdn.net/Code92007/article/details/87892307
题解
可以说是poj2348 Euclid's Game和Every-SG的合体了
不妨一堆为a,一堆为b,且设a>=b
(1)若a大于等于两倍的b,
则先手可以把局面由(a,b)①转移到(b,a%b)②或(b+(a%b),b)③
而又由③只能转移到②,说明②③的胜负是相反的,即②③必有一先手胜必有一先手败
①只需转到先手败那个状态即可实现先手胜,故先手必胜,
特别地,若sg[b][a%b]==0,则转移到(b,a%b)
而sg[b][a%b]==1,则转移到(b+(a%b),b),再由对手一步转移到(b,a%b)
故step[a][b]=1+sg[b][a%b]+step[b][a%b]
(2)若a在b和2*b之间,显然(a,b)胜负与(b,a%b)相反,且只有这一种转移方式
故step[a][b]=step[b][a%b]+1
Every-SG游戏
即多个没结束的游戏一起玩,每个没结束的游戏都必须决策,
且必胜的局面希望玩久一点,必败的局面希望玩快一点
若v的后继为u,则
①step[v]=0,v为终止状态
②step[v]=max(step[u])+1,v为必胜态,u为v的后继,会挑步数最多的后继转移
③step[v]=min(step[u])+1,v为必败态,u为v的后继,会挑步数最少的后继转移
Every-SG定理
我们认为,最后一个结束的游戏中胜者是最终的获胜者,
那么,先手必胜,当且仅当该单一游戏的最大step为奇数
口胡证明以下三条:
①先手必胜step为奇数,后手必胜step为偶数,
数学归纳法显然,一步是先手必胜,两步是后手必胜,剩下的都是先后手轮流走
②设最大的step为max,既然max存在,说明后手只能一次令max最多减一步,
而先手一次也不得不令max减一步,说明先手可以达到这个max
③设最大的step为max,则其它必败游戏中最大step2<=max,
而这在后手必胜的情况下,先手只能一次令step2最多减一步,后手不得不令step2减一步,
同理说明后手也可达(step2-1),又因为step2<=max,故先手最多在max步结束其必败游戏
心得
算是做了一个Every-SG的例题叭
最大步长为奇数先手胜
也是更深入理解SG了
也学会了一些~i即i!=-1的技巧性写法等等
代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1e3+5;
int n,sg[maxn][maxn],step[maxn][maxn];
int getsg(int a,int b)//a>=b
{
if(~sg[a][b])return sg[a][b];
if(!b)return sg[a][b]=0;
int k=a/b,r=a%b;//a=kb+r
if(k==1)
{
sg[a][b]=getsg(b,a%b)^1;
step[a][b]=step[b][a%b]+1;
return sg[a][b];
}
else
{
step[a][b]=getsg(b,a%b)+step[b][a%b]+1;//getsg一定放在前,不然不知道step[b][a%b]
return sg[a][b]=1;
}
}
int main()
{
memset(sg,-1,sizeof(sg));
while(~scanf("%d",&n))
{
int a,b,mx=0;
while(n--)
{
scanf("%d%d",&a,&b);
if(a<b)swap(a,b);
getsg(a,b);
mx=max(step[a][b],mx);
}
puts(mx&1?"MM":"GG");
}
return 0;
}