AcWing 1321. 取石子

原题链接
考察:博弈论
思路有点跳跃,反正我是没想到()
思路:
  我们发现操作1:石子数-1. 操作2:堆数-1.每次操作都变化的堆数+石子数的和的奇偶性.而堆数+石子数-1 = 能操作的最大次数.
  再发现当堆数 = 1,石子数 = 1是必经局面,先手必胜.堆数+石子数-1为奇.如果某个局面先手必胜,说明他最后能转到堆数 = 1,石子数 = 1的局面.那么考虑证明一个性质.令堆数+石子数-1 = b.若当前局面为b = 奇,那么一定能转到b = 偶的局面,而b = 偶后继一定为奇.

  1. b = 奇.
    1.1 堆数>1,合并堆数,堆数-1--->b为偶.
    1.2 堆数 = 1.若石子数 = 1,必胜.石子数>1,取石子b变为偶.
  2. 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;
}
posted @ 2021-05-27 12:10  acmloser  阅读(48)  评论(0编辑  收藏  举报