main
黄海 2 years ago
parent 8f454b820c
commit 2c276652a1

@ -1,59 +1,38 @@
#include <bits/stdc++.h> #include <iostream>
#include <cstring>
using namespace std; using namespace std;
const int N = 55, M = 50050; // M 包括了 50 * 1000 + 50个石子数量为1的堆数 const int N = 55, M = 50060;
int f[N][M]; int n, f[N][M];
int sg(int a, int b) {
int dfs(int a, int b) { if (f[a][b] != -1) return f[a][b];
int &v = f[a][b]; int &s = f[a][b];
if (~v) return v; if (!a) return b & 1; // 如果能转移到必败态
// 简单情况: 即所有堆的石子个数都是严格大于1此时a是0 if (b == 1) return sg(a + 1, b - 1);
if (!a) return v = b % 2; // 奇数为先手必胜,偶数为先手必败 if (a && !sg(a - 1, b)) return s = 1; // 取a
if (b && !sg(a, b - 1)) return s = 1; // 合并b,取b
// 一般5个情况 + 1个特殊情况 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
// 特殊情况: 如果操作后出现b中只有一堆且堆中石子个数为1 return s = 0;
// 那么应该归入到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;
} }
int main() { int main() {
int T;
scanf("%d", &T);
memset(f, -1, sizeof f); memset(f, -1, sizeof f);
int T, n; f[1][0] = f[2][0] = 1;
cin >> T; f[3][0] = 0;
while (T--) { while (T--) {
cin >> n; scanf("%d", &n);
int a = 0, b = 0; int a = 0, b = -1;
for (int i = 0; i < n; i++) { // n堆石子 for (int i = 1; i <= n; i++) {
int x; int x;
cin >> x; scanf("%d", &x);
if (x == 1) a++; // 每堆石子的数量 if (x == 1)
// b==0时 加1堆+加x石子=0 + 1+x-1=x a++;
// b!=0时 加1堆+加x石子=原来的+x+1
// 偏移量是1个在b=0时需要考虑引入这个偏移量:-1,在b>0时就不必再次考虑了
else else
b += b ? x + 1 : x; b += x + 1;
} }
if (b < 0) b = 0;
// 1 为先手必胜, 0为先手必败 if (sg(a, b))
if (dfs(a, b))
puts("YES"); puts("YES");
else else
puts("NO"); puts("NO");

@ -66,14 +66,20 @@ NO
**$Q3$:猜一下关联关系?** **$Q3$:猜一下关联关系?**
两个操作同一时间只能执行一个,可以理解为拿走一个石子对结果影响一下,合并两堆石子对结果也是影响一下,初步考虑应该堆个数与石子总数的加法关系相关。 两个操作同一时间只能执行一个,可以理解为拿走一个石子对结果影响一下,合并两堆石子对结果也是影响一下,初步考虑应该堆个数与石子总数的加法关系相关。
**一般情况:当每堆的石子个数都是大于等于$2$时,猜关联关系** **一般情况:当每堆的石子个数都是大于等于$2$时,猜关联关系**
<font size=5 color='red'><center><b>设$b$ = 堆数 + 石子总数 - $1$</b></center></font> <font size=5 color='red'><center><b> 剩余操作数 = $b$ = 堆数 + 石子总数 - $1$</b></center></font>
<font size=5 color='red'><center><b>结论:$b$是奇数⟺先手必胜,$b$是偶数⟺先手必败</b></center></font> <font size=5 color='red'><center><b>结论:$b$是奇数⟺先手必胜,$b$是偶数⟺先手必败</b></center></font>
我们可以发现,当$n$是$1$的时候,这样结论显然成立。 我们可以发现,当$n$是$1$的时候,也就是只有$1$堆时,比如$a_0=3$,那么$b=3+1-1=3$,是奇数:
- ① 先手拿走一个,剩操作数=$2$
- ② 后手只能拿走$1$个,剩操作数$1$
- ③ 先手再拿走1个剩余操作数$=0$
- ④ 后手没有可以拿的了,后手负,先手必胜!
然后分类讨论: 结论显然成立。
当不只有$1$堆时,分类讨论:
**情况$1$:没有数量为$1$的堆** **情况$1$:没有数量为$1$的堆**
@ -92,8 +98,6 @@ NO
这个情况比较复杂,因为如果某个人把$1$减少$1$,那么这个堆同时也消失了,相当于操作数减少了$2$。 这个情况比较复杂,因为如果某个人把$1$减少$1$,那么这个堆同时也消失了,相当于操作数减少了$2$。
本题中可能存在一些堆的石子个数等于$1$:
* 假设有$a$堆石子,其中每堆石子个数为$1$ * 假设有$a$堆石子,其中每堆石子个数为$1$
* 剩余堆的石子个数都严格大于$1$ * 剩余堆的石子个数都严格大于$1$
@ -108,6 +112,7 @@ $Q:$**情况**$3$**为什么是两个表达式?**
②当右侧一个都没有的时候,左边送来了一堆,两个石子,按$b$的定义,是堆数+石子个数$-1=2$,即$b+=2$ ②当右侧一个都没有的时候,左边送来了一堆,两个石子,按$b$的定义,是堆数+石子个数$-1=2$,即$b+=2$
### 六、实现代码 ### 六、实现代码
```cpp {.line-numbers} ```cpp {.line-numbers}
#include <bits/stdc++.h> #include <bits/stdc++.h>

Loading…
Cancel
Save