数学-博弈论. 拆分-Nim游戏
c++
AcWing 894. 拆分-Nim游戏
/*
* 题目描述:
* AcWing 894. 拆分-Nim游戏:
* 给定 n 堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,
* 然后放入两堆规模更小的石子(**新堆规模可以为 0,且两个新堆的石子总数可以大于取走的那堆石子数**),
* 最后无法进行操作的人视为失败。
* 问如果两人都采用最优策略,先手是否必胜。
*
* 输入格式:
* 第一行包含整数 n。
* 第二行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 ai。
*
* 输出格式:
* 如果先手方必胜,则输出 Yes。
* 否则,输出 No。
*
* 数据范围
* 1 ≤ n, ai ≤ 100
* 解题思路:
* 在 AcWing 893. 集合-Nim游戏 中,我们学习了 mex 运算 和 SG 函数,
* 并初步了解 SG 函数在判断必胜态和必负态的的作用我们还细究了 SG 函数为什了要求 mex 运算,这和普通 Nim 函数的关系;
* 那么在本题中,我们能否使用 SG 函数呢?
*
* 首先给定的了 n 堆石子 a1, a2, ..., an,只要我们求得 SG(a1), ..., SG(an) 通过与运算就可以得知最后的必胜态和必负态。
* 那么 SG(ai) 如何计算,ai 石子可以分为若干个状态,我们求解每个局面的 SG 函数,然后 mex 操作即可以求出当前局面的 SG 值。
* 但是,ai 分为的若干的局面是两堆石子,两堆石子如何计算 SG 函数?
*
* **SG函数理论**,多个独立局面的 SG 值,等于这些局面 SG 值的异或和。
*
* 由该理论可知,我哦们分别计算每堆石子的 SG 数值,然后两堆石子异或即可以求出两堆石子作为局面的 SG 数值。
*
* SG 函数理论我们不进行证明(因为我没看)
*
* 但是,如果细致的来看 普通 Nim 游戏的话,是可以看到 SG 函数和 Mex 操作的影子的。
* 首先,第 i 堆石子 ai 的 SG 函数 SG(ai) = ai。这是因为,他可以到达 ai - 1, ai - 2, ..., 0 多个局面
* SG(0) = 0, SG(1) = 1, ..., SG(x) = x
* 其次,如果将 a1 看成一个局面, a2 看成一个局面, ..., an 看成一个局面,这些独立局面的合并
* 即为多个局面的异或 a1 ^ a2 ... ^ an 就是我们最终的结果。
*
* 同理在集合 Nim 游戏中,最后 SG(a1) ^ SG(a2) ^ ... ^ SG(an) 也是多个独立局面 SG 数值的求解
*
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
using namespace std;
const int N = 110, M = 110;
int a[N], n;
int sg[M];
int get_sg(int x) {
if (sg[x] != -1) {
return sg[x];
}
set<int> s;
for (int i = 0; i < x; i ++ ) {
for (int j = 0; j <= i; j ++ ) {
s.insert(get_sg(i) ^ get_sg(j));
}
}
for (int i = 0; ; i ++ ) {
if (s.count(i) == 0) {
sg[x] = i;
break;
}
}
return sg[x];
}
bool solution() {
// initialize
int res = 0;
memset(sg, -1, sizeof sg);
// calculate
for (int i = 1; i <= n; i ++ ) {
res = (res ^ get_sg(a[i]));
}
return res != 0;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) {
scanf("%d", &a[i]);
}
if (solution()) {
puts("Yes");
} else {
puts("No");
}
return 0;
}