嘤嘤嘤这是我目前为止写过的最认真最仔细的一篇题解了。
题意:
中文题啦,所以不解释题意。
思路:
巴什博弈的简单变形,可以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;
}