diff --git a/TangDou/AcWing_TiGao/T5/GameTheory/1321.cpp b/TangDou/AcWing_TiGao/T5/GameTheory/1321.cpp index adb76c0..67a2816 100644 --- a/TangDou/AcWing_TiGao/T5/GameTheory/1321.cpp +++ b/TangDou/AcWing_TiGao/T5/GameTheory/1321.cpp @@ -1,59 +1,38 @@ -#include +#include +#include using namespace std; -const int N = 55, M = 50050; // M 包括了 50 * 1000 + 50个石子数量为1的堆数 -int f[N][M]; - -int dfs(int a, int b) { - int &v = f[a][b]; - if (~v) return v; - // 简单情况: 即所有堆的石子个数都是严格大于1,此时a是0 - if (!a) return v = b % 2; // 奇数为先手必胜,偶数为先手必败 - - // 一般5个情况 + 1个特殊情况 - - // 特殊情况: 如果操作后出现b中只有一堆,且堆中石子个数为1 - // 那么应该归入到a中,并且b为0 - // 以下所有情况,如果能进入必败态,先手则必胜! - if (b == 1) return dfs(a + 1, 0); - - // 情况1:有a,从a中取一个 - if (a && !dfs(a - 1, b)) return v = 1; - - // 情况2, 4:有b,从b中取1个(石子总数 - 1) or 合并b中两堆(堆数 - 1), - if (b && !dfs(a, b - 1)) return v = 1; - - // 情况3:有a >= 2, 合并a中两个 - // 如果b的堆数不为0, a - 2, b + 1堆 + 2个石子(只需要加delta) ====> b + 3 - // 如果b的堆数为0, a - 2, 0 + 2个石子 + 1堆 - 1 ====> b + 2 - if (a >= 2 && !dfs(a - 2, b + (b ? 3 : 2))) return v = 1; - - // 情况5:有a,有b, 合并a中1个b中1个, a - 1, b的堆数无变化 + 1个石子(只加delta) - if (a && b && !dfs(a - 1, b + 1)) return v = 1; - - // 其他情况,则先手处于必败状态 - return v = 0; +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); - int T, n; - cin >> T; + f[1][0] = f[2][0] = 1; + f[3][0] = 0; while (T--) { - cin >> n; - int a = 0, b = 0; - for (int i = 0; i < n; i++) { // n堆石子 + scanf("%d", &n); + int a = 0, b = -1; + for (int i = 1; i <= n; i++) { int x; - cin >> x; - if (x == 1) a++; // 每堆石子的数量 - // b==0时 加1堆+加x石子=0 + 1+x-1=x - // b!=0时 加1堆+加x石子=原来的+x+1 - // 偏移量是1个,在b=0时,需要考虑引入这个偏移量:-1,在b>0时,就不必再次考虑了 + scanf("%d", &x); + if (x == 1) + a++; else - b += b ? x + 1 : x; + b += x + 1; } - - // 1 为先手必胜, 0为先手必败 - if (dfs(a, b)) + if (b < 0) b = 0; + if (sg(a, b)) puts("YES"); else puts("NO"); diff --git a/TangDou/AcWing_TiGao/T5/GameTheory/1321.eddx b/TangDou/AcWing_TiGao/T5/GameTheory/1321.eddx new file mode 100644 index 0000000..e5f6838 Binary files /dev/null and b/TangDou/AcWing_TiGao/T5/GameTheory/1321.eddx differ diff --git a/TangDou/AcWing_TiGao/T5/GameTheory/1321.md b/TangDou/AcWing_TiGao/T5/GameTheory/1321.md index 759f309..e47be1e 100644 --- a/TangDou/AcWing_TiGao/T5/GameTheory/1321.md +++ b/TangDou/AcWing_TiGao/T5/GameTheory/1321.md @@ -66,14 +66,20 @@ NO **$Q3$:猜一下关联关系?** 两个操作同一时间只能执行一个,可以理解为拿走一个石子对结果影响一下,合并两堆石子对结果也是影响一下,初步考虑应该堆个数与石子总数的加法关系相关。 -**一般情况:当每堆的石子个数都是大于等于$2$时,猜的关联关系** +**一般情况:当每堆的石子个数都是大于等于$2$时,猜关联关系** -
设$b$ = 堆数 + 石子总数 - $1$
+
设 剩余操作数 = $b$ = 堆数 + 石子总数 - $1$
结论:$b$是奇数⟺先手必胜,$b$是偶数⟺先手必败
-我们可以发现,当$n$是$1$的时候,这样结论显然成立。 +我们可以发现,当$n$是$1$的时候,也就是只有$1$堆时,比如$a_0=3$,那么$b=3+1-1=3$,是奇数: +- ① 先手拿走一个,剩操作数=$2$ +- ② 后手只能拿走$1$个,剩操作数$1$ +- ③ 先手再拿走1个,剩余操作数$=0$ +- ④ 后手没有可以拿的了,后手负,先手必胜! -然后分类讨论: +结论显然成立。 + +当不只有$1$堆时,分类讨论: **情况$1$:没有数量为$1$的堆** @@ -92,8 +98,6 @@ NO 这个情况比较复杂,因为如果某个人把$1$减少$1$,那么这个堆同时也消失了,相当于操作数减少了$2$。 - -本题中可能存在一些堆的石子个数等于$1$: * 假设有$a$堆石子,其中每堆石子个数为$1$ * 剩余堆的石子个数都严格大于$1$ @@ -108,6 +112,7 @@ $Q:$**情况**$3$**为什么是两个表达式?** ②当右侧一个都没有的时候,左边送来了一堆,两个石子,按$b$的定义,是堆数+石子个数$-1=2$,即$b+=2$ + ### 六、实现代码 ```cpp {.line-numbers} #include