在算法竞赛的博弈论体系中,Nim 博弈是继巴什博弈之后的核心模型,也是公平组合游戏(ICG)中最经典的多堆取石子问题。如果说巴什博弈是入门博弈论的敲门砖,那么 Nim 博弈就是打通博弈论任督二脉的关键 —— 它跳出了单堆石子的限制,引入了异或运算这一核心工具,其思想更是延伸到阶梯型 Nim、Georgia and Bob 等经典变种问题中。本文将从 Nim 博弈的基础定义出发,深入推导核心结论,结合实战例题讲解代码实现,再剖析其经典变种的解题思路,让你彻底掌握 Nim 博弈的精髓,轻松解决各类多堆取石子问题。下面就让我们正式开始吧!
在正式讲解 Nim 博弈之前,我们先快速回顾博弈论的核心概念,这是理解 Nim 博弈的基础,尤其是公平组合游戏和必胜态 / 必败态的定义,Nim 博弈作为典型的公平组合游戏,完全遵循这两大核心原则。
满足以下三个条件的游戏即为公平组合游戏:
由于博弈论中默认玩家都是绝顶聪明的(会选择对自己最有利的最优策略),因此游戏的每个状态都有唯一的结果:
而推导必胜态和必败态的核心依据永远是两点:
Nim 博弈的核心结论,正是基于这一依据通过严格的数学推导得出,而非单纯的经验总结。
Nim 博弈的经典场景是多堆石子取物游戏,规则十分简洁:
有n 堆石子,每堆石子的数量分别为a1,a2,a3,...,an;两名玩家轮流进行操作,每次可以选择任意一堆石子,从中取走任意数量的石子(至少取 1 颗,最多可取完整堆);取走最后一颗石子的玩家获胜。问:先手玩家是否存在必胜策略?
与巴什博弈的单堆石子不同,Nim 博弈的核心是多堆石子的组合状态,其胜负判断不再依赖简单的取模运算,而是引入了位运算中的异或操作,这也是 Nim 博弈最精妙的地方。
先直接给出 Nim 博弈的终极结论,这是解决所有 Nim 博弈问题的核心,必须牢记:
对于 n 堆石子的 Nim 博弈,设每堆石子数为a1,a2,...,an,计算所有石子数的异或和:s=a1⊕a2⊕a3⊕...⊕an
其中⊕表示位运算中的异或操作(C++ 中用^符号表示),异或运算的核心规则是:相同为 0,不同为 1(如 1⊕1=0,1⊕0=1,0⊕0=0)。
举个简单的例子:
这两个例子正是后续实战例题的原型,我们会在代码实现中验证结果。
Nim 博弈的结论看似简单,但背后的数学证明至关重要,理解证明过程能让你真正掌握 Nim 博弈的思想,而非死记硬背结论。证明的核心思路依旧围绕必胜态和必败态的两大核心依据,分两步证明:
当所有堆的石子数都为 0 时(a1=a2=...=an=0),当前玩家无石子可取,处于必败态,此时异或和s=0,与结论一致。
即:当异或和s≠0时,当前玩家(先手)一定存在一种操作,使得操作后的异或和变为 0,将必败态交给后手。
详细推导:
由此可知,当 s≠0 时,先手总能找到这样一堆石子,通过取走部分石子让异或和变为 0,将必败态交给后手,因此s≠0 是必胜态。
即:当异或和 s=0 时,当前玩家(先手)无论采取何种操作,操作后的异或和一定不为 0,让后手进入必胜态。
详细推导:
由此可知,当 s=0 时,先手无论怎么操作,都会让异或和变为非 0,后手进入必胜态,因此s=0 是必败态。
至此,Nim 博弈的核心结论通过严格的数学证明得以验证,异或运算成为判断多堆取石子问题胜负的核心工具,这一思想也将贯穿所有 Nim 博弈的变种问题。
理论结合实践是掌握算法的关键,本节将结合两道经典的 Nim 博弈例题,从基础模板题到带操作方案的进阶题,逐一讲解解题思路,并附上完整的 C++ 代码实现,所有代码均可直接用于算法竞赛。
题目链接:https://ac.nowcoder.com/acm/problem/50615

牛客网(信息学奥赛一本通)
玩家为 2 人,道具为 N 堆石子,每堆石子数为X1,X2,...,XN,规则如下:
输出仅一行,若先手必胜输出win,后手必胜输出lose。
这是最标准的 Nim 博弈模板题,直接套用核心结论即可:
win;否则输出lose。#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int ret = 0; // 初始化异或和为0
for (int i = 0; i < n; i++) {
int x;
cin >> x;
ret ^= x; // 依次异或每堆石子数
}
if (ret) { // 异或和不为0,先手必胜
cout << "win" << endl;
} else { // 异或和为0,先手必败
cout << "lose" << endl;
}
return 0;
}输入:47 12 9 15
计算:7^12=11,11^9=2,2^15=13 ≠ 0 → 先手必胜
输出:win与题目示例一致,验证代码正确性。
题目链接:https://www.luogu.com.cn/problem/P1247
洛谷 P1247
输入 k 及 k 个整数n1,n2,...,nk,表示 k 堆火柴棒,每堆根数为ni;两名玩家轮流取火柴,规则与经典 Nim 博弈一致,取走最后一根火柴的玩家获胜。
要求:判断先手是否必胜,若必胜则输出第一次的操作方案(字典序最小),若必败则输出lose。
lose;本题是 Nim 博弈的进阶题,不仅要判断胜负,还要输出字典序最小的操作方案,解题思路分为两步:
lose;根据 Nim 博弈的证明,当异或和为 ret≠0 时,找到第一个满足a[i]⊕ret<a[i]的堆 i,就是字典序最小的堆,取出的数量为a[i]−(a[i]⊕ret)。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5e5 + 10; // 数据范围适配算法竞赛
int a[N]; // 存储每堆石子/火柴的数量
int main() {
int n;
cin >> n;
int ret = 0;
for (int i = 1; i <= n; i++) { // 堆数从1开始,符合题目输出习惯
cin >> a[i];
ret ^= a[i];
}
if (ret == 0) { // 异或和为0,先手必败
cout << "lose" << endl;
} else { // 寻找字典序最小的操作方案
for (int i = 1; i <= n; i++) {
int t = a[i] ^ ret;
if (t < a[i]) { // 找到符合条件的堆
int take = a[i] - t; // 取出的数量
cout << take << " " << i << endl;
a[i] = t; // 更新该堆的数量
break;
}
}
// 输出操作后的状态
for (int i = 1; i <= n; i++) {
cout << a[i] << " ";
}
cout << endl;
}
return 0;
}输入:33 6 9
计算:3^6^9 = 0 → 先手必败
输出:lose
掌握经典 Nim 博弈后,其各类变种问题都是在经典模型的基础上做规则变形,核心思路依旧是将变种问题转化为经典 Nim 博弈,其中阶梯型 Nim 博弈是最经典、最常考的变种,也是算法竞赛中的高频考点。
有1~n 级台阶,第 i 级台阶上放有a[i]个石子;两名玩家轮流进行操作,每次可以选择任意一级台阶,将其中任意数量的石子(至少 1 颗)移动到下一级台阶;石子移到地面(0 级台阶)后无法再移动,移走最后一颗石子的玩家获胜。问:先手是否存在必胜策略?
阶梯型 Nim 博弈的核心是忽略偶数级台阶,只考虑奇数级台阶的经典 Nim 博弈,结论十分简洁:
计算所有奇数级台阶的石子数的异或和:s=a[1]⊕a[3]⊕a[5]⊕...
简单来说,阶梯型 Nim 博弈的胜负,仅由奇数级台阶的石子分布决定,偶数级台阶只是 “中转站”,不影响最终结果。
为什么阶梯型 Nim 博弈只需要考虑奇数级台阶?我们可以通过必胜态 / 必败态的核心逻辑和模仿操作策略来通俗证明,无需复杂的数学推导:
由此可知,阶梯型 Nim 博弈的本质,就是奇数级台阶的经典 Nim 博弈。
阶梯型 Nim 博弈的代码实现十分简单,只需在经典 Nim 博弈的基础上,仅遍历奇数级台阶进行异或运算即可:
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int ret = 0;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
if (i & 1) { // 仅对奇数级台阶进行异或(i&1为1表示奇数)
ret ^= x;
}
}
if (ret) {
cout << "win" << endl; // 先手必胜
} else {
cout << "lose" << endl; // 先手必败
}
return 0;
}阶梯型 Nim 博弈的核心思想可以延伸到各类移动型取物游戏中,比如格子移石子、棋盘移棋子等,这类问题的解题关键是将问题转化为阶梯型 Nim 博弈,找到对应的 “奇数级台阶”。本节将结合两道经典例题,讲解阶梯型 Nim 博弈的实际应用。
题目链接:https://ac.nowcoder.com/acm/problem/218562

牛客网
有 1~n 个格子,每个格子中有若干石子;两名玩家轮流操作,每次将第 i 格中的任意数量石子(至少 1 颗)移动到第 i+1 格;将最后一颗石子移到最后一格的玩家获胜。lyw 为先手,问谁会赢?
本题是阶梯型 Nim 博弈的经典变形,解题的关键是重新定义 “台阶”:
因此,只需计算从 n-1 开始的奇数位格子的石子数异或和,若异或和≠0 则先手(lyw)必胜,否则后手必胜。
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int main() {
int T;
cin >> T; // 多组测试用例
while (T--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int ret = 0;
// 从n-1开始,步长为-2,遍历奇数位
for (int i = n - 1; i >= 1; i -= 2) {
ret ^= a[i];
}
if (ret) {
cout << "lyw" << endl; // 先手必胜
} else {
cout << "zgc" << endl; // 后手必胜
}
}
return 0;
}题目链接:https://www.luogu.com.cn/problem/P10507
洛谷 P10507
有一个无限长的棋盘,从左到右编号为 1,2,3,...;棋盘上有 n 个棋子,位置互不相同;两名玩家轮流操作,每次将一枚棋子向左移动至少 1 格,不能逾越其他棋子,不能与其他棋子重合,不能移出棋盘;无法操作的玩家判负,Georgia 为先手,问谁会赢?
本题是阶梯型 Nim 博弈的经典进阶变形,看似是移棋子,实则可以转化为台阶移石子,解题分为三步:
棋子的向左移动,等价于阶梯型 Nim 中石子的向下移动,相邻棋子的空格数就是台阶上的石子数,而偶数位的空格数就是决定胜负的 “奇数级台阶”。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int p[N], a[N];
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> p[i];
}
sort(p + 1, p + 1 + n); // 排序棋子位置
int ret = 0;
for (int i = 1; i <= n; i++) {
a[i] = p[i] - p[i-1] - 1; // 计算空格数
}
// 从后往前遍历奇数位的空格数
for (int i = n; i >= 1; i -= 2) {
ret ^= a[i];
}
if (ret) {
cout << "Georgia will win" << endl;
} else {
cout << "Bob will win" << endl;
}
}
return 0;
}Nim 博弈的精髓不在于死记硬背异或和的结论,而在于理解其背后的必胜态 / 必败态推导逻辑,以及将变种问题转化为经典模型的思维能力。从单堆的巴什博弈到多堆的 Nim 博弈,再到移动型的阶梯型 Nim 博弈,博弈论的学习始终围绕 “找规律、做转化” 两大核心,而这也是算法竞赛的核心能力。 希望本文能帮助你彻底掌握 Nim 博弈的基础原理和经典变种,在后续的博弈论学习中打下坚实的基础。后续我会继续讲解 SG 函数、威佐夫博弈等进阶模型,关注我,一起玩转算法竞赛的博弈论!