HDU 3595 GG and MM

题意:GG和MM玩游戏。游戏的规则:两堆石子,从中选择一堆石子,其个数为x,从另一堆石子中拿出kx个石子(k为任意整数,且kx小于等于该堆的数目),不能取的人为输。两个人会同时玩多个游戏,所有游戏必须玩完,且在最后一个结束的游戏中胜利的人,为全场的胜者。如果两个人采取最优策略,求谁是最后的胜者。

思路:先考虑单个游戏。

          设两堆石子分别为x,y(x > y)。1.如果x/y的商为1,那取石子的人只有一种选择,就是在x中取出y,下一个局面为,x-y,y,这个过程就是辗转相除的过程;  2.如果x/y的商大于1,即大于等于2,那他有多种选择可以选。所以他可以通过取不同数目的石子来让自己达到最优的局面,让1< x / y <2,让自己永远不会输。所以谁首先达到这个局面,谁就是胜者。从最初的局面开始,如果经过偶数次选择,那就是先手必胜。如果经过奇数次选择,那就是后手必胜。


在考虑多个游戏。

由于多个游戏必须要玩完,所以每个游戏的时间会影响最后的结果。整个游戏的胜负就是持续时间最长的子游戏的胜负。不难想到,必胜策略如下:对于我可以必胜的子游戏,我要玩足够长的时间,对于我必输的子游戏,我要用最短的时间去结束游戏。

 那现在的问题是,如果去控制子游戏时间的长短?根据上面对单个游戏的讨论,我们可以发现,关键点还是在x / y 的商大于等于2时。在这个时候,对于胜者,我们可以通过其他选择,而不是直接辗转相除,让游戏在延长一回合。同时,为了能最大限度的延长游戏的时间,我们可以控制选择,来让每次的x/y的商大于等于2的时机在自己的回合里。

那,如何判断这样的时机是否在自己的回合中?可以记录上一次出现该时机的基础回合个数的奇偶性。当再次出现这样的时机,如果基础回合的奇偶性相同,那就是在自己的回合中,不需要增加附加的回合。如果奇偶性不同,那需要增加附加的回合来保证这样的时机在自己的回合中出现。

由上述讨论可知,每个子游戏的总回合数就是基础回合数(根据辗转相除法求出)+附加回合数(在辗转相除的过程中,对关键时机的处理)。

同样,对于整个游戏,如果最长的子游戏的回合数为偶数,那先手胜,为奇数,后手胜。


#include <cstdio>
#include <algorithm>
#define eps 1e-13
#define MAXN 1000005
using namespace std;

int ans, step, cnt;
int Eucliud(int x, int y)
{
	if (y&&x / y > 1){
		if (ans >= 0 && (cnt & 1) != ans) step++;
		ans = cnt & 1;
	}
	cnt++;
	return y == 0 ? x : Eucliud(y, x%y);
}
int main()
{
	int t, n, k, a, b;
	while (~scanf("%d", &n)){
		int ret = 0;
		for (int i = 0; i < n; i++){
			ans = -1; step = 0, cnt = 0;//ans的-1表示开始状态,step为基础回合数,cnt为附加回合数
			scanf("%d%d", &a, &b);
			if (a < b) swap(a, b);
			Eucliud(a, b);
			ret = max(ret, step + cnt);
		}
		printf((ret & 1) ?"GG\n" : "MM\n");
	}
	return 0;
}

思路2:在noi的集训队论文:《组合游戏略述 ——浅谈SG游戏的若干拓展及变形》中,提到过这种游戏的模型叫:every-sg;

其中也给出了对于游戏中step的计算,这里就不多说了。

说一个小的细节:由于这里实际上利用的每个状态的sg值是否为0,同时最后对胜负条件的判断和sg无关,只和step最大值的奇偶有关,所以每个状态的sg值可以只有两个状态0和1,且没有必要用sg定理把真正的sg值求出来。


#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int MAX = 1010;

int step[MAX][MAX];
int sg[MAX][MAX];//0表示先手必败,1表示先手必胜

int EverySG(int a, int b)
{
	if (sg[a][b] >= 0) return sg[a][b];
	if (a < b) swap(a, b);
	int mi = 0x3f3f3f3f;
	int ma = 0;
	for (int i = b; i <= a; i += b){
		if (EverySG(b, a - i) == 0){//后继状态为先手必败,那该状态为先手必胜,求step最大值
			ma = max(ma, step[b][a - i]);
			sg[a][b] = sg[b][a] = 1;
		}
		else mi = min(mi, step[b][a - i]);//否则求step最小值
	}
	if (sg[a][b] == 1){
		step[a][b] = step[b][a] = ma + 1;
		return 1;
	}
	step[a][b] = step[b][a] = mi + 1;
	return sg[a][b] = sg[b][a] = 0;
}



int main(void)
{
	int n;
	memset(sg, -1, sizeof(sg));
	for (int i = 0; i < MAX; ++i)
		step[i][0] = step[0][i] = sg[i][0] = sg[0][i] = 0;//初始化
	while (scanf("%d", &n) != EOF){
		int mm = 0;
		while (n--){
			int a, b;
			scanf("%d %d", &a, &b);
			EverySG(a, b);
			mm = max(mm, step[a][b]);
		}
		printf((mm & 1) ? "MM\n" : "GG\n");
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值