题解:AcWing 891 Nim游戏

【题目来源】

AcWing:891 Nim游戏 - AcWing题库

【题目描述】

给定 \(n\) 堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

【输入】

第一行包含整数 \(n\)

第二行包含 \(n\) 个数字,其中第 \(i\) 个数字表示第 \(i\) 堆石子的数量。

【输出】

如果先手方必胜,则输出 Yes

否则,输出 No

【输入样例】

2
2 3

【输出样例】

Yes

【核心思想】

  1. 问题分析:给定 \(n\) 堆石子,两位玩家轮流从任意一堆中取走任意数量的石子(至少取 \(1\) 个),无法操作者失败。这是一个经典的** impartial game(公平组合游戏),属于Nim 游戏。问题的关键在于判断先手是否必胜**。

  2. 算法选择

    • Nim 游戏定理(Bouton's Theorem):对于 \(n\) 堆石子的 Nim 游戏,先手必胜当且仅当所有堆石子数量的异或和(XOR sum)不为 \(0\)。形式化地,设第 \(i\) 堆石子数为 \(a_i\),则先手必胜 \(\iff a_1 \oplus a_2 \oplus \ldots \oplus a_n \neq 0\)
    • Sprague-Grundy 定理的特例:Nim 游戏中每堆石子的 Grundy 数(或称 nimber)就是该堆的石子数本身,整个游戏的 Grundy 数为各堆 Grundy 数的异或和
  3. 关键步骤

    • 读取输入:石子堆数 \(n\) 和每堆的石子数量 \(a_1, a_2, \ldots, a_n\)
    • 计算异或和:初始化 res = 0,依次将每堆石子数与 res 进行异或:res ^= a_i
    • 判断胜负
      • res != 0,输出 Yes(先手必胜)
      • res == 0,输出 No(先手必败)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),只需遍历 \(n\) 堆石子,每次进行 \(O(1)\) 的异或运算
    • 空间复杂度:\(O(1)\),仅使用一个整型变量存储异或结果
  5. Nim 游戏与异或和的核心思想

    • P-position 与 N-position
      • P-position(Previous-player win,必败态):前一个玩家(即刚刚完成操作的玩家)有必胜策略。在 Nim 游戏中,异或和为 \(0\) 的状态是 P-position
      • N-position(Next-player win,必胜态):下一个玩家(即将操作的玩家)有必胜策略。在 Nim 游戏中,异或和不为 \(0\) 的状态是 N-position
    • 必胜策略的构造性证明
      • 若当前异或和 \(S = a_1 \oplus a_2 \oplus \ldots \oplus a_n \neq 0\),设 \(S\) 的最高位为第 \(k\) 位,则必存在某堆 \(a_i\) 的第 \(k\) 位也为 \(1\)。令 \(a_i' = a_i \oplus S\),则 \(a_i' < a_i\)(因为最高位被消去),且新的异或和为 \(0\)。因此先手总可以将局面变为 P-position
      • 若当前异或和 \(S = 0\),则任何操作都会改变某一堆的值,使异或和变为非 \(0\)。因此后手总能将局面恢复为 P-position
    • 终止状态:全 \(0\) 状态的异或和为 \(0\),是 P-position,即最后操作的人获胜
    • 适用于:多堆取石子游戏、异或和判胜负的公平组合游戏

【算法标签】

博弈论

【代码详解】

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int n;           // 石子堆数
    cin >> n;

    int res = 0;     // 异或结果,初始为0

    // 读取每堆石子的数量
    while (n--)
    {
        int x;       // 当前堆的石子数量
        cin >> x;
        res ^= x;    // 将所有石子数量进行异或运算
    }

    // Nim博弈定理:
    // 如果所有石子数量的异或结果不为0,先手必胜
    // 如果所有石子数量的异或结果为0,先手必败
    if (res)  // 如果res != 0
    {
        cout << "Yes" << endl;  // 先手必胜
    }
    else
    {
        cout << "No" << endl;   // 先手必败
    }

    return 0;
}

【运行结果】

2
2 3
Yes
posted @ 2026-06-23 11:25  团爸讲算法  阅读(9)  评论(0)    收藏  举报