5.3 KiB
一、题目描述
Alice
和 Bob
两个好朋友又开始玩取石子了。
游戏开始时,有 N
堆石子排成一排,然后他们轮流操作(Alice
先手),每次操作时从下面的规则中任选一个:
- 从某堆石子中取走一个;
- 合并任意两堆石子。
不能操作的人输。
Alice
想知道,她是否能有必胜策略。
输入格式
第一行输入 T
,表示数据组数。
对于每组测试数据,第一行读入 N
;
接下来 N
个正整数 a_1,a_2,⋯,a_N
,表示每堆石子的数量。
输出格式 对于每组测试数据,输出一行。
输出 YES
表示 Alice
有必胜策略,输出 NO
表示 Alice
没有必胜策略。
数据范围
1≤T≤100,1≤N≤50,1≤a_i≤1000
输入样例:
3
3
1 1 2
2
3 4
3
2 3 5
输出样例:
YES
NO
NO
二、博弈论总结
必胜态 \Rightarrow
选择合适方案 \Rightarrow
必败态
必败态 \Rightarrow
选择任何路线 \Rightarrow
必胜态
三、本题题解
先来考虑 简单情况:所有堆的石子个数 >1
设 b
= 堆数 + 石子总数 − 1
可以推出: 先手必胜 \Leftrightarrow
b
是奇数
证明:考虑以下结论:
α
. 任意一个是奇数的情况 可以 被转换成一个偶数的情况 (一定存在一个偶数后继)
β
. 任意一个是偶数的情况 一定 会被转换成一个奇数的情况 (所有后继都是奇数)。
先来考虑结论 α.
- 如果堆数
>1
,可以合并其中两堆石子,这样b
就会变成偶数 - 如果堆数
=1
,就可以拿掉1
个石子,这样b
也会变成偶数
再来考虑结论 β
.
- 合并其中的两堆石子:显然,
b
会变成奇数 - 取了一个石子:
(1) 取得堆中石子个数
>2
:石子个数− 1
,b
会变成奇数 (2) 取得堆中石子个数=2
:石子个数变成了1
(继续分情况讨论):- 只有一堆:
b
是奇数,对手必胜 - 多于一堆:
b
是奇数,对手可以把剩下的一个石子放到其他的堆里面去,对手同样必胜
- 只有一堆:
这样,我们成功的证明除了: 先手必胜 \Leftrightarrow
(b
是奇数)
接下来考虑一般情况:有些堆的石子个数可能 =1
假设石子个数 =1
的堆数为 a
,b
仍然表示剩余石子的 操作数,即 (堆数 +
石子总数 − 1
)
把所有石子分成两个区域,一区域的数量 =1
(对应 a
),二区域的数量都 >1
(对应 b
).
那么可以写出以下转移:
f(a,b)
:
① 从 a
中取 1
个 →f(a−1,b)
② 从 b
中取 1
个 →f(a,b−1)
③ 合并 b
中的 2
个 →f(a,b−1)
④ 合并 a
中 2
个 (b
堆石子数 +2
,堆数 +1
) →f(a−2,b+3)
⑤ 合并 a
中 1
个 b
中 1
个 (b
堆石子数 +1
, a
个数 −1
)→f(a−1,b+1)
Code
#include <bits/stdc++.h>
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;
}
int main() {
memset(f, -1, sizeof f);
int T, n;
cin >> T;
while (T--) {
cin >> n;
int a = 0, b = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
if (x == 1) a++;
// b != 0时 加1堆 + 加x石子 = 原来的 + x + 1 (其实就是区别一开始的时候)
// 当b != 0时, 我们往后加的delta
// b == 0时 加1堆 + 加x石子 = 0 + 1 + x - 1 = x
// 注意操作符优先级
else
b += b ? x + 1 : x;
}
// 1 为先手必胜, 0为先手必败
if (dfs(a, b))
puts("YES");
else
puts("NO");
}
return 0;
}