取石子
取石子
首先我们看一下:
简单情况:所有堆的石子个数>1
设 \(b=\) 堆数+石子总数-1,
那么我们可以确定当 \(b\) 是奇数时,那就是必胜局面。
因为我们合并堆的话要堆数次,取石子的话要取石子总数次,加起来就是总共需要的操作次数,然后有的时候为不仅取一个堆,还会去掉石子,所以减掉1。
接下来说明为什么当 \(b\) 是奇数时,那就是必胜局面。
-
任何一个奇数的状态都一定存在一个偶数后继。
-
任何一个偶数所有后继必然是奇数。
这里插入一个特例,
只有一堆且这一堆只有一个,\(b=1+1-1=1\) 那么这个东西就是一个必胜局面。
证明1
- 如果堆数大于1,那么我们就合并两堆
- 如果堆数等于1,那么就取一个石子就可以了。
证明2:
- 合并两堆
- 取一个石子
- 取石子前的个数>2的话,就可以了。
- 如果等于2
- \(2-1=1\) 如果为一堆的话根据特例就一定可以
- 多余一堆就直接合并即可
我们接下来的话继续讨论第二种也就是说
石子个数为1的堆,我们将这种堆的数量为 \(a\) 。
我们设 \(f(a,b)\) 来进行递推(\(a,b\) 定义见上)
- 从 \(a\) 中取一个,\(f(a-1,b)\)
- 从 \(b\) 中取一个 \(f(a, b-1)\)
- 合并 \(b\) 中的两个 \(f(a,b-1)\)
- 合并 \(a\) 中的两个 \(f(a-2,b+3)\)
- 合并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;
}

浙公网安备 33010602011771号