hdu3595 every-sg
传送门:https://vjudge.net/problem/HDU-3595
最近都在做博弈论的题目,然后从汇总(https://vjudge.net/article/501)上看到every-sg的题,就来写写了。
其实一开始是看网上最顶上的博客才看懂的(https://blog.csdn.net/qiqijianglu/article/details/7959723),这篇博客所说的其实是:every-sg问题就是若干个游戏共同进行,以最后结束的游戏结果为最后结果,所以对于每一个游戏,必胜态所走的步数是后继必败态步数最大+1,必败态步数是后继中最小必胜态步数+1。这个的确是对的。
但是看到大神的代码,发现枚举后继状态是一个一个找过去的,也就是i+=a这句话。感觉不妥,遂思考。
因为这题就相当于辗转相除法,a/b >=2 是必胜的(因为如果(a%b,b)是必胜,那么也就是(a%b+b,b)是必败,那么可以转到必败态,而如果(a%b,b)必败,那也可以转到必败态)。
而a/b==1的情况,就只能看(a%b,b)的状态了。也就是说必胜态后继只有一个必败态,也只能转到这个必败态,必败态后继只有一个必胜态,也只能转到这个必胜态,所以每个状态玩到结束的步数是确定的。所以枚举之后的状态感觉有点没必要。。。。(但是对于every-sg来说,思路是对的)
那么我们要看什么呢?问题是最后结束的游戏,所以我们要看每一个游戏的步数(之前说过每个游戏的步数是确定的),然后记录最多步数那一个,奇数先手赢,偶数后手赢。
都在注释里了,看代码吧。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int st[1000+8][1000+8];//每个状态玩到最后的步数 4 int find(int a,int b){ //找每个状态玩到最后的步数 5 if(a<b) swap(a,b); 6 if(st[a][b]>=0) return st[a][b]; //记忆化搜索 7 if(a/b==1) return st[a][b]=find(a%b,b)+1;//如果a/b==1,只能转到 (a%b,b) 8 int tem=find(a%b,b); //记录(a%b,b)的步数 9 if(tem&1) st[a][b]=tem+2; //tem奇数,就是(a%b,b)必胜,所以转到(a%b+b,b)这个必败态 10 else st[a][b]=tem+1;//转到必败态,因为后继只有这个必败态了 ,总不可能转去必胜态给对手吧。 11 return st[b][a]=st[a][b]; 12 } 13 int main(){ 14 int n; 15 memset(st,-1,sizeof(st)); 16 for(int i=0;i<=1000;++i) st[i][0]=st[0][i]=0; //其中一堆没有了,步数就是0啦 17 while((~scanf("%d",&n)) && n){ 18 int ans=0; 19 for(int i=1;i<=n;++i){ 20 int a,b;scanf("%d %d",&a,&b); 21 if(a==0 || b==0) continue; 22 ans=max(ans,find(a,b));//记录最多步数那个游戏 23 } 24 if(ans&1) puts("MM"); 25 else puts("GG"); 26 } 27 return 0; 28 }