题目地址:
https://www.acwing.com/problem/content/893/
给定 n n n堆石子,两个玩家轮流操作,每次可以从任意一堆石子中拿走任意数量的石子,可以拿完但不能不拿。无法操作者视为失败。问先手是否存在必胜策略。
数据范围:
1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1≤n≤105
1 ≤ x ≤ 1 0 9 1\le x\le 10^9 1≤x≤109
x x x是每堆石子个数
Nim游戏模型,有个结论,设每堆石子个数分别是 a 1 , . . . , a n a_1,...,a_n a1,...,an,那么当且仅当 a 1 ∧ a 2 ∧ . . . ∧ a n ≠ 0 a_1\wedge a_2\wedge ... \wedge a_n\ne 0 a1∧a2∧...∧an=0的时候先手有必胜策略。
算法正确性证明:
首先容易证明所有 32 32 32位整数与异或运算 ∧ \wedge ∧构成一个阿贝尔群,并且单位元是 0 0 0(参考https://blog.csdn.net/qq_46105170/article/details/104082406)。接下来证明三个结论:
1、如果 a 1 = a 2 = . . . = a n = 0 a_1=a_2=...=a_n=0 a1=a2=...=an=0,那么 a 1 ∧ a 2 ∧ . . . ∧ a n = 0 a_1\wedge a_2\wedge ... \wedge a_n= 0 a1∧a2∧...∧an=0。
这一点显然;
2、如果 a 1 ∧ a 2 ∧ . . . ∧ a n ≠ 0 a_1\wedge a_2\wedge ... \wedge a_n\ne 0 a1∧a2∧...∧an=0,一定存在一种取法使得取完之后 a 1 ∧ a 2 ∧ . . . ∧ a n = 0 a_1\wedge a_2\wedge ... \wedge a_n= 0 a1∧a2∧...∧an=0。
设 a 1 ∧ a 2 ∧ . . . ∧ a n = x a_1\wedge a_2\wedge ... \wedge a_n=x a1∧a2∧...∧an=x,由于 x ≠ 0 x\ne 0 x=0,我们取 x x x的最高位的 1 1 1,那么一定存在某个 a i a_i ai在这一二进制位也等于 1 1 1,并且 a i ∧ x < a i a_i\wedge x<a_i ai∧x<ai(因为 a i a_i ai的二进制最高位被清零了),所以我们可以从第 i i i堆石子里取掉 a i − ( a i ∧ x ) a_i-(a_i\wedge x) ai−(ai∧x)这么多石子,这样第 i i i堆石子就剩下 a i ∧ x a_i\wedge x ai∧x这么多石子,而 a 1 ∧ a 2 ∧ . . . ∧ ( a i ∧ x ) ∧ . . . ∧ a n = x ∧ x = 0 a_1\wedge a_2\wedge ... \wedge(a_i\wedge x)\wedge... \wedge a_n= x\wedge x=0 a1∧a2∧...∧(ai∧x)∧...∧an=x∧x=0,这样取完之后所有石子数异或就等于 0 0 0了。
3、如果 a 1 ∧ a 2 ∧ . . . ∧ a n = 0 a_1\wedge a_2\wedge ... \wedge a_n= 0 a1∧a2∧...∧an=0,那么无论怎么取,取完之后一定有 a 1 ∧ a 2 ∧ . . . ∧ a n ≠ 0 a_1\wedge a_2\wedge ... \wedge a_n\ne 0 a1∧a2∧...∧an=0。
反证法,如果不然,设取的是第 i i i堆石子,那么有 a 1 ∧ a 2 ∧ . . . ∧ ( a i ′ ) ∧ . . . ∧ a n = 0 a_1\wedge a_2\wedge ... \wedge(a_i')\wedge... \wedge a_n=0 a1∧a2∧...∧(ai′)∧...∧an=0,由于 a 1 ∧ a 2 ∧ . . . ∧ ( a i ) ∧ . . . ∧ a n = 0 a_1\wedge a_2\wedge ... \wedge(a_i)\wedge... \wedge a_n=0 a1∧a2∧...∧(ai)∧...∧an=0,两边异或得 a i ∧ a i ′ = 0 a_i\wedge a_i'=0 ai∧ai′=0,说明 a i = a i ′ a_i=a_i' ai=ai′,没取石子,这是不允许的。
由于这个游戏里石子个数一定会不断减少,所以游戏必然会结束。如果 a 1 ∧ a 2 ∧ . . . ∧ a n ≠ 0 a_1\wedge a_2\wedge... \wedge a_n\ne 0 a1∧a2∧...∧an=0,那么先手可以每次都使得后手面对 a 1 ∧ a 2 ∧ . . . ∧ a n = 0 a_1\wedge a_2\wedge... \wedge a_n= 0 a1∧a2∧...∧an=0的局面,而后手走完后,先手又会面对 a 1 ∧ a 2 ∧ . . . ∧ a n ≠ 0 a_1\wedge a_2\wedge... \wedge a_n\ne 0 a1∧a2∧...∧an=0的局面,这样一来,先手总能保持一直遇到异或非 0 0 0的局面,而后手一定每次都遇到异或是 0 0 0的局面,所以最后停止的时候也是后手遇到 a 1 = . . . = a n = 0 a_1=...=a_n=0 a1=...=an=0的局面,这个局面里后手输,所以先手必胜。如果 a 1 ∧ a 2 ∧ . . . ∧ a n = 0 a_1\wedge a_2\wedge... \wedge a_n= 0 a1∧a2∧...∧an=0,那么后手可以让先手每次都遇到异或是 0 0 0的局面,所以后手有必胜策略,先手必败。
代码如下:
#include <iostream>
using namespace std;
const int N = 100010;
int a[N];
int main() {
int n;
cin >> n;
int res = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
res ^= a[i];
}
cout << (res == 0 ? "No" : "Yes") << endl;
return 0;
}
时间复杂度 O ( n ) O(n) O(n),空间 O ( 1 ) O(1) O(1)。