题目链接

【模板】Nim游戏

题目描述

堆石子,第 堆有 个。Alice 和 Bob 两位玩家轮流行动,Alice 先手。每人每次可以从任意一堆中取走任意正整数个石子(也可以取完)。取走最后一颗石子的玩家获胜。假设双方都采取最优策略,判断先手能否必胜。

解题思路

本题是组合博弈论中最经典的模型——Nim 游戏。所有公平组合博弈(Impartial Games)的问题都可以通过 Sprague-Grundy 定理转化为 Nim 游戏。

对于 Nim 游戏本身,有一个简洁而优美的结论来判断胜负: 一个 Nim 游戏的局面是必败态,当且仅当所有石子堆数量的异或和 (XOR sum) 等于 0。

这个异或和在博弈论中被称为 Nim 和 (Nim-Sum)

定理证明思路

我们定义一个局面的 Nim 和为 ,其中 是按位异或操作。

  1. 终局状态:游戏结束时,所有石子堆都为 0,此时 。根据规则,轮到当前玩家时已无石子可取,因此该玩家判负。所以,Nim 和为 0 的终局是一个必败态,这与定理一致。

  2. 从任意 的局面,总能一步转移到 的局面: 假设当前 Nim 和 。设 的二进制表示中最高位的 1 在第 位。那么,在所有石子堆中,必然存在至少一堆 ,其二进制表示的第 位也为 1(否则 的第 位不可能是 1)。 此时,玩家可以选择第 堆,并从中取走一些石子,使其数量变为

    • 因为 的最高有效位相同,所以 ,这意味着 是一个比 更小的非负数。这是一个合法的操作(取走了 个石子)。
    • 操作后,新的 Nim 和变为
    • 这证明了,任何一个 Nim 和不为 0 的局面都是必胜态,因为玩家总能将一个 Nim 和为 0 的局面留给对手。
  3. 从任意 的局面,无论如何操作,都会转移到 的局面: 假设当前 Nim 和 。玩家必须选择一堆 并将其变为 )。

    • 操作后,新的 Nim 和变为
    • 因为 ,所以 ,即
    • 这证明了,任何一个 Nim 和为 0 的局面都是必败态,因为玩家无论如何操作,都必然会将一个 Nim 和不为 0 的局面留给对手。

最终结论: Alice 作为先手,只需计算初始时所有石子堆的 Nim 和。

  • 如果 Nim 和不等于 0,则为必胜态,Alice 胜
  • 如果 Nim 和等于 0,则为必败态,Bob 胜

代码

#include <iostream>
#include <vector>
#include <numeric>

using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    int t;
    cin >> t;
    while (t--) {
        int k;
        cin >> k;
        int nim_sum = 0;
        for (int i = 0; i < k; ++i) {
            int a;
            cin >> a;
            nim_sum ^= a;
        }
        if (nim_sum == 0) {
            cout << "NO" << endl;
        } else {
            cout << "YES" << endl;
        }
    }
    return 0;
}
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int t = sc.nextInt();
        while (t-- > 0) {
            int k = sc.nextInt();
            int nimSum = 0;
            for (int i = 0; i < k; i++) {
                nimSum ^= sc.nextInt();
            }
            if (nimSum == 0) {
                System.out.println("NO");
            } else {
                System.out.println("YES");
            }
        }
    }
}
import sys

def solve():
    t_str = sys.stdin.readline()
    if not t_str:
        return
    t = int(t_str)
    for _ in range(t):
        k_str = sys.stdin.readline()
        if not k_str: break
        
        line = sys.stdin.readline()
        if not line: break
        
        nums = map(int, line.split())
        nim_sum = 0
        for num in nums:
            nim_sum ^= num
            
        if nim_sum == 0:
            print("NO")
        else:
            print("YES")

solve()

算法及复杂度

  • 算法: 博弈论 (Nim 游戏)
  • 时间复杂度: 对于每组测试数据,我们需要读取 个数字并计算它们的异或和,时间复杂度为 。总的时间复杂度为
  • 空间复杂度: 我们只需要一个变量来存储异或和,空间复杂度为