取石子

取石子

首先我们看一下:

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

b= 堆数+石子总数-1,

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

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

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

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

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

这里插入一个特例,

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

证明1

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

证明2:

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

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

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

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

  1. a 中取一个,f(a1,b)
  2. b 中取一个 f(a,b1)
  3. 合并 b 中的两个 f(a,b1)
  4. 合并 a 中的两个 f(a2,b+3)
  5. 合并1个 a, 一个 b f(a1,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; }

__EOF__

本文作者ljfyyds
本文链接https://www.cnblogs.com/ljfyyds/p/16928527.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   ljfyyds  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示