取石子

取石子

首先我们看一下:

简单情况:所有堆的石子个数>1

\(b=\) 堆数+石子总数-1,

那么我们可以确定当 \(b\) 是奇数时,那就是必胜局面。

因为我们合并堆的话要堆数次,取石子的话要取石子总数次,加起来就是总共需要的操作次数,然后有的时候为不仅取一个堆,还会去掉石子,所以减掉1。

接下来说明为什么当 \(b\) 是奇数时,那就是必胜局面。

  1. 任何一个奇数的状态都一定存在一个偶数后继。

  2. 任何一个偶数所有后继必然是奇数。

这里插入一个特例,

只有一堆且这一堆只有一个,\(b=1+1-1=1\) 那么这个东西就是一个必胜局面。

证明1

  1. 如果堆数大于1,那么我们就合并两堆
  2. 如果堆数等于1,那么就取一个石子就可以了。

证明2:

  1. 合并两堆
  2. 取一个石子
  3. 取石子前的个数>2的话,就可以了。
  4. 如果等于2
    1. \(2-1=1\) 如果为一堆的话根据特例就一定可以
    2. 多余一堆就直接合并即可

我们接下来的话继续讨论第二种也就是说

石子个数为1的堆,我们将这种堆的数量为 \(a\)

我们设 \(f(a,b)\) 来进行递推(\(a,b\) 定义见上)

  1. \(a\) 中取一个,\(f(a-1,b)\)
  2. \(b\) 中取一个 \(f(a, b-1)\)
  3. 合并 \(b\) 中的两个 \(f(a,b-1)\)
  4. 合并 \(a\) 中的两个 \(f(a-2,b+3)\)
  5. 合并1个 \(a\), 一个 \(b\) \(f(a-1,b+1)\)
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 55, M = 50050;
int f[N][M];

int dp(int a, int b)
{
    int &v = f[a][b];
    if(v != -1) return v;
    if(!a) return v = b % 2;
    if(b == 1) return dp(a + 1, 0);
    
    if(a && !dp(a - 1, b)) return v = 1;
    if(b && !dp(a, b - 1)) return v = 1;
    if(a >= 2 && !dp(a - 2, b + (b ? 3 : 2))) return v = 1;
    if(a && b && !dp(a - 1, b + 1)) return v = 1;
    
    return v = 0;
}

int main()
{
    memset(f, -1, sizeof(f));
    int T;
    scanf("%d", &T);
    while(T -- )
    {
        int n;
        scanf("%d", &n);
        int a = 0, b = 0;
        for(int i = 0; i < n; i ++ )
        {
            int x;
            scanf("%d", &x);
            if(x == 1) a ++ ;
            else b += b ? x + 1 : x;//如果b大于0,那么就加x+1(还会有一堆),否则加x
        }
        if(dp(a, b))
            puts("YES");
        else
            puts("NO");
    }
    return 0;
}
posted @ 2022-11-26 22:34  ljfyyds  阅读(46)  评论(0)    收藏  举报