【ACWing】891. Nim游戏

题目地址:

https://www.acwing.com/problem/content/893/

给定 n n n堆石子,两个玩家轮流操作,每次可以从任意一堆石子中拿走任意数量的石子,可以拿完但不能不拿。无法操作者视为失败。问先手是否存在必胜策略。

数据范围:
1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1n105
1 ≤ x ≤ 1 0 9 1\le x\le 10^9 1x109
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 a1a2...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 a1a2...an=0
这一点显然;
2、如果 a 1 ∧ a 2 ∧ . . . ∧ a n ≠ 0 a_1\wedge a_2\wedge ... \wedge a_n\ne 0 a1a2...an=0,一定存在一种取法使得取完之后 a 1 ∧ a 2 ∧ . . . ∧ a n = 0 a_1\wedge a_2\wedge ... \wedge a_n= 0 a1a2...an=0
a 1 ∧ a 2 ∧ . . . ∧ a n = x a_1\wedge a_2\wedge ... \wedge a_n=x a1a2...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 aix<ai(因为 a i a_i ai的二进制最高位被清零了),所以我们可以从第 i i i堆石子里取掉 a i − ( a i ∧ x ) a_i-(a_i\wedge x) ai(aix)这么多石子,这样第 i i i堆石子就剩下 a i ∧ x a_i\wedge x aix这么多石子,而 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 a1a2...(aix)...an=xx=0,这样取完之后所有石子数异或就等于 0 0 0了。
3、如果 a 1 ∧ a 2 ∧ . . . ∧ a n = 0 a_1\wedge a_2\wedge ... \wedge a_n= 0 a1a2...an=0,那么无论怎么取,取完之后一定有 a 1 ∧ a 2 ∧ . . . ∧ a n ≠ 0 a_1\wedge a_2\wedge ... \wedge a_n\ne 0 a1a2...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 a1a2...(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 a1a2...(ai)...an=0,两边异或得 a i ∧ a i ′ = 0 a_i\wedge a_i'=0 aiai=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 a1a2...an=0,那么先手可以每次都使得后手面对 a 1 ∧ a 2 ∧ . . . ∧ a n = 0 a_1\wedge a_2\wedge... \wedge a_n= 0 a1a2...an=0的局面,而后手走完后,先手又会面对 a 1 ∧ a 2 ∧ . . . ∧ a n ≠ 0 a_1\wedge a_2\wedge... \wedge a_n\ne 0 a1a2...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 a1a2...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)

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/114006771