Nim Game

Nim游戏

Nim游戏是博弈论中最经典、最重要的模型之一,也是巴什博弈的多维推广


一、问题定义

  • 多堆 石子(比如 3 堆、4 堆...)

  • 每堆石子数量分别为 a1,a2,...,aka1​,a2​,...,ak​

  • 每人每次选择 其中一堆 ,取 任意正整数颗(至少1颗,至多全取完)

  • 取光者赢(标准规则)

  • 双方最优策略,问先手是否必胜

与巴什博弈的区别

维度 巴什博弈 Nim游戏
堆数 1堆 多堆
每步限制 1 ~ m 颗 任意颗(整堆)
核心结论 模周期 异或和

二、核心结论(必背!)

Nim游戏的结论极其简洁优美:

text

复制代码
若 a₁ XOR a₂ XOR a₃ XOR ... XOR aₖ == 0
则 先手必败(P态)
否则 先手必胜(N态)

其中 XOR按位异或运算。


三、为什么是异或?直观理解

1. 终局状态

  • 终局:所有堆都是 0

  • 0⊕0⊕...⊕0=00⊕0⊕...⊕0=0 ✅ 必败态

2. 必败态性质

当异或和为 0 时:

  • 无论你从哪一堆取多少颗(不能为0),新状态的异或和 一定 ≠ 0

3. 必胜态性质

当异或和 ≠ 0 时:

  • 一定存在一种取法,使新状态的异或和变为 0(证明用到了异或的数学性质)

4. 核心逻辑链

text

复制代码
必败态(0) → 只能走到必胜态(≠0)
必胜态(≠0) → 存在一步走到必败态(0)

四、举例验证

例子1:3堆 (3, 4, 5)

text

复制代码
3 ⊕ 4 ⊕ 5 = ?
3 = 011
4 = 100  → 异或 = 111(7) ⊕ 5
5 = 101
--------
7 ⊕ 5 = 111 ⊕ 101 = 010(2) ≠ 0

先手 必胜

例子2:3堆 (3, 5, 6)

text

复制代码
3 = 011
5 = 101  → 异或 = 110(6) ⊕ 6 = 0
6 = 110

先手 必败


五、获胜策略(如何走)

当异或和 ≠ 0 时,先手要找到 某一堆,使其数量变为:

text

复制代码
新数量 = 原数量 ⊕ 总异或和

条件:新数量 < 原数量(因为只能减少)

例:(3, 4, 5) 异或和 = 2

  • 堆1: 3 ⊕ 2 = 1 (< 3) ✅ 可取,将3变为1 → 新状态(1,4,5) 异或=0

  • 堆2: 4 ⊕ 2 = 6 (> 4) ❌ 不能

  • 堆3: 5 ⊕ 2 = 7 (> 5) ❌ 不能

所以唯一正确走法:从第1堆取2颗,变成 (1,4,5)


六、取光者输(反常Nim / Misère Nim)

特殊情况

所有堆都只有 1 颗石子 时,规则特殊:

text

复制代码
- 若堆数 % 2 == 0 → 必胜
- 若堆数 % 2 == 1 → 必败

一般情况(不全是 1)

结论 与标准Nim相同

text

复制代码
异或和 == 0 → 先手必败
否则 → 先手必胜

完整判断流程

python

复制代码
def misere_nim(stones):
    # stones: 列表,每堆石子数
    all_one = all(x == 1 for x in stones)
    
    if all_one:
        return len(stones) % 2 == 1  # 奇数堆输?
        # 实际上:奇数堆 → 先手必败(输出NO)
        # 需要根据题目要求调整
    else:
        xor_sum = 0
        for x in stones:
            xor_sum ^= x
        return xor_sum != 0

七、常见变形与考点

变形 结论变化
标准Nim(取光者赢) 异或和判断
Misère Nim(取光者输) 全1时单独处理,否则同标准
每次只能取 ≤ m 颗(每堆有上限) 转化为每个堆的Grundy数,再异或(SG定理)
多堆 + 每堆取法不同 SG定理通用解法
可以同时从多堆取 规则变化,不一定是Nim

八、与巴什博弈的关系

巴什博弈是 1堆的Nim游戏(但每步有限制)

巴什博弈→Nim的关键推广:

  • 1堆(有上限) → 巴什

  • 多堆(无上限) → Nim

  • 多堆(每堆有上限) → 需要SG定理

text

复制代码
SG定理核心:
1. 每堆独立看作一个子游戏
2. 计算每堆的SG值
3. 所有SG值异或 == 0 → 先手败

九、一句话总结

Nim游戏 = 多堆石子的异或和运算
异或和 = 0 → 先手必败
先手策略:让下一状态异或和变为 0


十、快速判断模板

python

复制代码
def nim_win(stones):
    xor_sum = 0
    for x in stones:
        xor_sum ^= x
    return "YES" if xor_sum != 0 else "NO"

掌握了Nim,你就掌握了组合博弈论的核心工具 ,可以进一步学习SG定理,解决几乎所有公平组合游戏问题。

如果只能从最左端或者最右端取呢

很好的问题!这个变种规则完全改变了博弈的性质。

问题重定义

  • 一排 石子(或数组/序列)

  • 每堆石子有不同数量(不是每堆独立,而是排在一条线上)

  • 每次只能从 最左端最右端整堆(或取这堆里任意数量?需要明确)

  • 取光者赢

⚠️ 关键区分 :你问的实际上是 "取石子游戏"的另一个经典变种,不再是 Nim/巴什的范畴。

你需要先确认题目具体规则:


情况1:每次从一端取 整堆(堆大小固定)

比如序列:[3, 5, 2, 7, 4]

规则

  • 每次只能拿最左或最右的 那一整堆

  • 拿走该堆全部石子

  • 取光者赢

这是什么问题?

完全信息双人零和博弈 ,可以用 区间DP 解决

解法:先手最大得分差

dp[i][j] 表示当前剩余区间 [i, j] 时,先手能获得的最大净胜分(自己得分 - 对方得分)

转移:

text

复制代码
dp[i][j] = max(
    a[i] - dp[i+1][j],   // 取左端
    a[j] - dp[i][j-1]    // 取右端
)

边界:dp[i][i] = a[i]

最终判断:dp[0][n-1] > 0 → 先手必胜

举例

[3, 9, 1, 2]

计算:

  • dp[3][3]=2

  • dp[2][3]=max(1-2, 2-1)=max(-1,1)=1

  • dp[1][3]=max(9-1, 2-1)=max(8,1)=8

  • dp[0][3]=max(3-8, 2-8)=max(-5,-6)=-5 → 先手必败


情况2:从一端取 1~m 颗(该端堆无限/足够多)

比如一长排石子,每堆数量很大

规则

  • 每次只能从最左端或最右端的堆里取 1~m 颗

  • 取光者赢

本质

退化成了巴什博弈?不完全是,因为两端独立

实际上这是一个 "两端巴什" 问题,也可以用区间DP + SG定理分析


常见算法竞赛真题形式

题目1:两端取整堆(LeetCode 877. Stone Game)

  • 总石子数奇数

  • 每次取左或右整堆

  • 得分高者赢

结论 :先手 必胜(因为有对称策略)


题目2:两端取任意正整数颗(POJ 1067 取石子游戏)

  • 两堆石子

  • 每次可从一堆取任意 或 从两堆取相同数量

  • 威佐夫博弈(Wythoff Game)

结论:黄金比例判断


总结:不同限制条件下的结论

规则 类型 判断方法
多堆独立,每堆可取任意 Nim 异或和
单堆,取1~m 巴什 n % (m+1)
一排,只能取两端整堆 区间DP dp[i][j] 正负
一排,每端取1~m 区间DP + SG 复杂
两堆,取任意/相同 威佐夫 黄金分割
相关推荐
研究点啥好呢1 小时前
Momenta算法工程师面试题精选:10道高频考题+答案解析
人工智能·算法·求职招聘·面试笔试
Resistance丶未来1 小时前
DeepSeek-V4 新手快速上手指南
数据结构·python·gpt·算法·机器学习·claude·claude 4.6
无限进步_2 小时前
【C++】寻找数组中出现次数超过一半的数字:三种解法深度剖析
开发语言·c++·git·算法·leetcode·github·visual studio
comli_cn2 小时前
HMM算法
线性代数·算法
人道领域2 小时前
【LeetCode刷题日记】150.逆波兰表达式求值
java·数据结构·算法·leetcode
蓝桉~MLGT2 小时前
中级软考(软件工程师)算法特辑——常考的六大基础排序算法
java·算法·排序算法
minji...2 小时前
Linux 网络套接字编程(五)TCP 回声服务器的实现(单进程(单线程)/多进程/多线程/线程池四个版本)
linux·服务器·开发语言·网络·c++·tcp/ip·算法
嘻嘻哈哈樱桃2 小时前
牛客经典101题题解集--堆/栈/队列
java·开发语言·算法
凯瑟琳.奥古斯特2 小时前
常见排序算法性能对比
数据结构·算法·排序算法