+1学姐玩跳棋

嘤嘤嘤这是我目前为止写过的最认真最仔细的一篇题解了。

题意:

中文题啦,所以不解释题意。

思路:

巴什博弈的简单变形,可以SG打表找找规律。其实不会巴什博弈和SG也可以做出来。

但是为了便于讲解,这里还是简单介绍一下。

巴什博弈:

甲和乙一块报数,每人每次最少报1个,最多报4个,看谁先报到30。

其实这个游戏的胜负只和先手后手有关。比如第一轮报数,甲报k个数,那么乙报5-k个数,那么乙报数之后,问题就变为,甲和乙一块报数,看谁先报到25,进而变为20,15,10,5,当到5的时候,不管甲怎么报数,最后一个数肯定是乙报的,可以看出,作为后手的乙在个游戏中是不会输的。

那么如果我们要报到n,每次最少报1个,最多报m个,那么如果n是m+1的整数倍,则先手必败;否则,先手必胜。

巴什博奕:有一堆石子,数量为n,两个人轮流取石子,规定每次最少取1个,最多取m个,最后取光者为胜。

本题就相当于有一堆数量为n-1的石子,三场的取物规则不同,最后取光者为胜。

SG函数:

必胜点和必败点的概念: 

P点:必败点,换而言之,就是谁处于此位置,则在双方操作正确的情况下必败。 

Ñ点:必胜点,处于此情况下,双方操作均正确的情况下必胜。

如果一个状态对甲来说是必胜点,则对乙来说是必败点。如果不指明是对哪个人,则一般是指对当前状态的先手。

必胜点和必败点的判定:

1. 终态为必败点 P 。(意思是没有后继状态了,没有办法继续操作了,所以肯定是必败态。)

2. 如果存在一种操作可以进入必败点 P 的为必胜态 N 。(因为都足够理智,如果存在一种操作可以令对手处于必败态的话,他肯定会这样做的。)

3. 无论如何操作都只能进入必胜点 N 的状态为必败点 P 。(不管他怎么操作,下一个状态即对手的状态都是必胜态,则此人必败。)

SG函数定义:

首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex {0,1,2,4} = 3,mex {2,3,5} = 0,mex {} = 0。

对于任意状态x,定义SG(x)= mex(S),其中S是x后继状态的SG函数值的集合。如x有三个后继状态分别为a,b,c,那么SG(x)= mex {SG(a),SG(b),SG(c)}。x没有后继状态<=>集合S的后继状态的SG函数值的集合是空集<=> SG(x)= mex {} = 0 <=> x为必败点P.

不太明白没关系,下面以本题的第三场战斗为例跑一下。

物品总数为n3-1,每次可以取c个或d个,最后取光者为胜。我们定义自变量x为物品个数。

显然sg(0)= 0(即如果没有物品了,肯定是必败态),

SG(X)= {mex SG(x-c),SG(x-d)}(当前状态为物品数为x,如果操作是拿走c个,则下一状态为物品数为x-c;如果操作是拿走d个,则下一状态为物品数为x-d。我们来看一下SG函数的合理性。假如后继状态有必败态即当前状态为必胜态,而sg(x)= mex {0,一堆正整数}显然大于0;假如后继状态都是必胜态即当前状态为必败态,而sg(x)= mex {一堆正整数}显然等于0)

SG函数的自变量是可以随便定义的,看心情

所以第三场的代码是:

//mp本质是sg函数啦,为了方便写代码,mp的取值只有0和1,即必败态和必胜态。
//mp初始值都为0
//因为每一个状态只能由更小的x转化而来,所以该状态是不是必胜态,只需看它的前驱状态有没有必败态
for(int i=1;i<=n3;++i)
	if(!mp[i])
	{
		if(i+c<=n3)mp[i+c]=1;
		if(i+d<=n3)mp[i+d]=1;	
	}
flag3=mp[n3];

然后第一场和第二场可以类似打表找出规律:

第一场:

 n sg( n )
[ 1,a ]0
[ a + 1,2a ]1
[ 2a + 1,3a ]0
[ 3a + 1,4a ]1
.............
[偶数个 a + 1,奇数个 a ]0
[奇数个 a + 1,偶数个 a ]1

 

代码是:

flag1=((n1-1)/a)&1;

第二场:

如果b是奇数的话,只需要判断 n2的奇偶性,n2为偶数则必胜;

如果b是偶数,则 n2每 b + 1会出现0101010 .......... 101010的循环,所以只需要先将 n2%( b + 1),再判断奇偶性,若为偶数则必胜。

由于一个数模一个偶数,则这个数奇偶性不变,所以b为奇数时,也可以先模 b + 1,再判断奇偶。

代码是:

flag2=((n2%(b+1))&1)^1;

           

代码:

#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e3+10;
int mp[N];
int main()
{
	int t,n1,n2,n3,a,b,c,d,flag1,flag2,flag3;
	scanf("%d",&t);
	while(t--)
	{
		memset(mp,0,sizeof(mp));
		scanf("%d%d%d%d%d%d%d",&n1,&n2,&n3,&a,&b,&c,&d);
        flag1=((n1-1)/a)&1;
		flag2=((n2%(b+1))&1)^1;
		for(int i=1;i<=n3;++i)
			if(!mp[i])
			{
				if(i+c<=n3)mp[i+c]=1;
				if(i+d<=n3)mp[i+d]=1;	
			}
		flag3=mp[n3];
		if(flag1&&flag2&&flag3)
			printf("YES\n");
		else 
			printf("NO\n");
	}
	return 0;
 }

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值