数学: 博弈论. Nim游戏
C++
AcWing 891. Nim游戏
/*
题目描述:
891. Nim游戏:
给定 n 堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式:
第一行包含整数 n。
第二行包含 n 个数字,其中第 i 个数字表示第 i 堆石子的数量。
输出格式:
如果先手方必胜,则输出 Yes。
否则,输出 No。
数据范围:
1 ≤ n ≤ 10^5,
1 ≤ 每堆石子数 ≤ 10^9
解题思路:
对于这种 Nim 游戏,首先需要考虑的是 必负态,
如果能让对手到达 必负态 的就是必胜态,否则就是必负态,加入必负态集合中。
从题目可知, n 堆石子全为 0 的状态就是必负态,本题不方便进行推导,我先给出公式后证明。
设 n 堆石子数量为 a1, a2, ..., an,倘若 a1 ^ a2 ^ ... ^ an = 0,表示先手必负,否则先手必胜。(其中 ^ 表示二进制异或)
证明:
1. 当 n 堆石子 全为 0,是必负态,满足 a1 ^ a2 ^ ... ^ an = 0 的条件。
2. 当 a1 ^ a2 ^ ... ^ an = x,并且 x != 0 时,我们来证明其是必胜态:
a1 ^ a2 ^ ... ^ an = x 等式左侧,必定存在 ai,满足 x 的二进制最高位 1 在 ai 中也为 1。
那么可得 (ai ^ x) < ai。因此 a1 ^ a2 ^ ... ^ an = x 等式两侧同时异或上 x,可得
a1 ^ a2 ^ ... ^ an ^ x = 0
a1 ^ a2 ^ ... ^ (ai ^ x) ^ ... ^ an = 0
因此,我们在第 i 堆中可以拿出 ai - (ai ^ x)个石子,来使得剩余堆石子异或为 0
3. a1 ^ a2 ^ ... ^ an = 0条件下,我们拿出任意个石子,都不可能保持 异或 等于 0 的条件
假如说我们从第i堆取出一些石子(大于 0 ),数量从 ai 变为 bi ,我们假设异或仍旧等于 0
a1 ^ a2 ^ ... ^ ai ^ ... ^ an = 0
a1 ^ a2 ^ ... ^ bi ^ ... ^ an = 0
我们两式合并 (a1 ^ a2 ^ ... ^ ai ^ ... ^ an) ^ (a1 ^ a2 ^ ... ^ bi ^ ... ^ an) = 0
化简之后可得 ai ^ bi = 0,因此可以得到 ai = bi,然而 bi 是严格小于 ai 的,所以说假设不成立。
综上可知 a1 ^ ... ^ an = 0 为必负态,其余是必胜态。这是因为必胜态可以让对手达到必负态,必负态只能让对手达到必胜态
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int a[N], n;
bool solution() {
int res = 0;
for (int i = 1; i <= n; i ++ ) {
res = (res ^ a[i]);
}
if (res == 0) {
return false;
} else {
return true;
}
}
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;
}