AcWing 1321. 取石子
原题链接
考察:博弈论
思路有点跳跃,反正我是没想到()
思路:
我们发现操作1:石子数-1. 操作2:堆数-1.每次操作都变化的堆数+石子数的和的奇偶性.而堆数+石子数-1 = 能操作的最大次数.
再发现当堆数 = 1,石子数 = 1是必经局面,先手必胜.堆数+石子数-1为奇.如果某个局面先手必胜,说明他最后能转到堆数 = 1,石子数 = 1的局面.那么考虑证明一个性质.令堆数+石子数-1 = b.若当前局面为b = 奇,那么一定能转到b = 偶的局面,而b = 偶后继一定为奇.
- b = 奇.
1.1 堆数>1,合并堆数,堆数-1--->b为偶.
1.2 堆数 = 1.若石子数 = 1,必胜.石子数>1,取石子b变为偶. - b = 偶.
2.1 堆数>1,合并堆数,b为奇.
2.2 不合并(堆数不一定<2),取石子.
2.2.1 取的堆石子数>1,那么还给对方仍然为奇数.
2.2.2 取得石子数 = 1,那么奇偶性就不会改变,要避免这种事情,就必须限制局面所有石子数>1.但是存在取完石子数 = 1的情况.此时对方可以合并石子,那么所有石子数又>1.
综上考虑,我们限定b为石子数>1的所有堆的最大操作数.如果b为奇,操作者给对手的石子数每堆一定>1.所以不考虑石子数 = 1的堆,若b为奇,先手必胜.
考虑石子数 = 1的堆.我们定义石子数 = 1的堆数为a,那么每个不同的局面都可以用a,b表示.每个局面都有其他局面递推而来,由此考虑记忆化搜索的方式f[a,b]递推局面的胜负性.考虑每个操作对f[a,b]的影响即可.
写的时候会发现取b存在取完b只剩1堆只有1个的情况,此时本应该是a+1,b-3.之前说了可以通过合并保持不变,那么a合并又变成a+1-2,b不变.变成新状态f[a-1][b].但是此操作先手不一定必胜,如果不是必胜就没必要合并,所以这里没想出为什么不用讨论这个情况.
注意合并a,b堆里的石子一定要b>1,a>0
Code
#include <iostream>
#include <cstring>
using namespace std;
const int N = 55,M = 50060;
int n,f[N][M];
int sg(int a,int b)
{
if(f[a][b]!=-1) return f[a][b];
int& s = f[a][b];
if(!a) return b&1;//如果能转移到必败态
if(b==1) return sg(a+1,b-1);
if(a&&!sg(a-1,b)) return s = 1;//取a
if(b&&!sg(a,b-1)) return s = 1;//合并b,取b
if(a&&b>1&&!sg(a-1,b+1)) return s = 1;//合并a,b
if(a>1&&!sg(a-2,b==0?b+2:b+3)) return s = 1;//合并a
return s = 0;
}
int main()
{
int T;
scanf("%d",&T);
memset(f,-1,sizeof f);
f[1][0] = f[2][0] = 1; f[3][0] = 0;
while(T--)
{
scanf("%d",&n);
int a = 0,b = -1;
for(int i=1;i<=n;i++)
{
int x; scanf("%d",&x);
if(x==1) a++;
else b+=x+1;
}
if(b<0) b = 0;
if(sg(a,b)) puts("YES");
else puts("NO");
}
return 0;
}