题目地址:
https://www.acwing.com/problem/content/893/
给定 n n n堆石子,两个玩家轮流操作,每次可以从任意一堆石子中拿走任意数量的石子,可以拿完但不能不拿。无法操作者视为失败。问先手是否存在必胜策略。
输入格式:
第一行包含整数
n
n
n。第二行包含
n
n
n个数字,其中第
i
i
i个数字表示第
i
i
i堆石子的数量。
输出格式:
如果先手方必胜,则输出“Yes”。
数据范围:
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的二进制的与
x
x
x最高位的位置被清零了,更高位不变),所以我们可以从第
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;
int n;
int main() {
scanf("%d", &n);
int res = 0;
while (n--) {
int x;
scanf("%d", &x);
res ^= x;
}
res ? puts("Yes") : puts("No");
}
时间复杂度 O ( n ) O(n) O(n),空间 O ( 1 ) O(1) O(1)。