#题解_U687148 玄武阁的棋子博弈
【题解】U687148 玄武阁的棋子博弈(Nim 游戏完全解析)
题目速览
题目来源:洛谷 U687148 / 经典博弈论
核心规则:
有 \(N\) 堆棋子,第 \(i\) 堆有 \(A_i\) 个。
两人轮流操作,每次任选一堆并拿走任意正整数个(可全拿)。
无法合法操作(棋子全空)者输。两人均采取最优策略。
目标:判断先手是必胜(Win)还是必败(Lose)。
结论先行
如果所有堆棋子数量的按位异或和(XOR Sum)为 0,则先手必败(Lose);否则先手必胜(Win)。
\(S \neq 0 \implies \text{Win}\)
\(S = 0 \implies \text{Lose}\)
很多同学看到这个结论的第一反应是:“凭什么用异或?凭什么异或和为 0 就是必败态?”
本篇题解将从最简单的直觉入手,带你一步步推导并严格证明这个博弈论中的经典结论。
- 建立直觉:从 1 堆和 2 堆看起
遇到看似复杂的多堆问题,最有效的方法是化简。
1.1 只有 1 堆棋子
先手直接一次性把这一堆拿空。
此时棋子全无,后手无法操作,先手必胜。
1.2 只有 2 堆棋子
设两堆数量分别为 \((A_1, A_2)\):
情况 1:两堆数量相等,如 \((3, 3)\)
先手无论在哪一堆拿走多少个,后手只要在另一堆拿走相同数量即可。
后手执行的是一种绝对的“镜像模仿策略”。无论先手怎么做,后手总能把局面再次拉回两堆相等的局面。
直到先手拿光其中一堆,后手随即拿光另一堆,先手无子可拿而落败。
结论:两堆相等时,先手必败。
情况 2:两堆数量不等,如 \((3, 5)\)
先手手握主动权,只需把 5 的那堆拿走 2 个,局面立刻变成 \((3, 3)\) 丢给后手。
此时后手变成了面对“两堆相等”的人,后手必败,即先手必胜。
核心直觉:所谓的“必败态”,本质上是一种完美的对称平衡。谁拿到平衡局面,谁动一下就会打破平衡;而对方总能通过操作再次恢复平衡。
- 深入本质:多堆棋子如何定义“对称”?
当棋子变成 3 堆甚至更多堆时(如 \(2, 3, 4\)),我们很难一眼看出怎样才算“对称”。
数学家 Charles L. Bouton 在 1901 年给出了精妙的解法:将每一堆棋子拆解为二进制数(即 \(1, 2, 4, 8 \dots\) 的砝码组合)。
以 \(2, 3, 4\) 为例:
\(2 = (010)_2\)
\(3 = (011)_2\)
\(4 = (100)_2\)
我们按二进制位统计“1”出现的次数:
\(2^0\) 位(个位):只有 3 的末位是 1,共 1 个(奇数)
\(2^1\) 位(次位):2 和 3 的该位是 1,共 2 个(偶数)
\(2^2\) 位(高位):只有 4 的该位是 1,共 1 个(奇数)
如果某个二进制位上的 1 是偶数个,说明这个权重的“砝码”是成对成对存在的,可以视作微观上的“对称”。如果每一个二进制位上的 1 都是偶数个,整个棋局就达到了多维度的究极平衡。
而按位异或运算(\(\oplus\))的规则,恰好就是“不进位的二进制加法”:
相同为 0,相异为 1。
统计某一位上 1 的个数:偶数个 1 异或得 0,奇数个 1 异或得 1。
因此,异或和为 0,就完全等价于“每个二进制位上 1 的个数都是偶数”这一平衡状态。
- 严格证明:为什么起手的异或和决定胜负?
很多同学可能会有这样的疑问:
“终局全为 0 的异或和确实是 0。但这关起手的异或和什么事?难道不能中途把局面变成别的样子吗?”
博弈论(有向无环图博弈,Impartial Game)给出了严格的状态判定准则。一个局面是必败态(P 态)还是必胜态(N 态),必须且只需满足以下三条铁律:
终局边界:无法进行任何合法移动的状态是必败态(P 态)。
P 态不可保持:从任意一个必败态出发,无论采取何种合法操作,都只能转移到必胜态(N 态)。
N 态具有可转移性:从任意一个必胜态出发,一定存在至少一种合法操作,可以转移到必败态(P 态)。
我们来严谨检验“异或和 \(S = 0\)”是否完全满足这三条性质:
性质 1:终局边界
游戏结束时,所有堆的棋子都为 0。
此时轮到谁谁就无子可拿而判负。性质 1 成立。
性质 2:异或和为 0 的局面,走一步必然打破 0
假设当前局面的异或和为:
现在轮到某位玩家操作,按照规则,他必须从第 \(k\) 堆中拿走一部分棋子,使该堆数量从 \(A_k\) 减少为 \(A'_k\)(即 \(0 \le A'_k < A_k\))。
操作后的新异或和记作 \(S'\):
利用异或运算的性质,\(S'\) 可以表示为:
因为当前 \(S = 0\),所以:
由于必须拿走棋子,故 \(A'_k \neq A_k\),因此必定有:
结论:只要你接手的是异或和为 0 的局面,无论你动哪一堆、拿多少个,新的异或和必然不为 0。你绝不可能直接把一个异或和为 0 的局面甩给对手。性质 2 成立。
性质 3:异或和不为 0 的局面,一定能一步把它做成 0
假设当前局面异或和 \(S \neq 0\)。
设 \(S\) 在二进制表示下的最高有效位是第 \(d\) 位(即第 \(d\) 位是 1,而更高位全是 0)。
根据异或定义,所有堆在第 \(d\) 位上的 1 相加必为奇数,因此必定存在至少一堆棋子 \(A_k\),其二进制的第 \(d\) 位也是 1。
我们考虑把这堆棋子的数量修改为:
我们需要验证两点:
这是合法操作吗(棋子数是否严格减少)?
因为 \(A_k\) 和 \(S\) 的最高位都是第 \(d\) 位,二者在该位上都是 1。
异或之后,第 \(d\) 位变成了 0,而比第 \(d\) 位更高的位两者都是 0,异或后依然是 0。
最高位的 1 被消掉了,因此必定满足:
数量确实变少了,符合规则!
操作后新的异或和会变成 0 吗?
确实变成了 0!
结论:只要当前异或和非 0,玩家一定能精准找到一堆棋子并减少它,使新局面异或和恰好变成 0。性质 3 成立。
逻辑闭环:为什么起手决定生死?
根据以上三条性质,整个对弈过程就变成了一个锁死的逻辑链条:
[先手面对 S ≠ 0] --(性质3: 主动变0)--> [后手面对 S = 0]
↑ |
| (性质2: 被迫变非0)
| |
+------------(不断循环)----------------+
↓
[最终后手面对全 0]
(后手无法操作,输)
如果起手 \(S \neq 0\):
先手只要按照性质 3 的策略,每次都把异或和变为 0 甩给后手。
后手无论怎么挣扎,按照性质 2,操作完后异或和必然又变成非 0。
先手再次利用性质 3 调整回 0。
如此往复,每次轮到后手时异或和都必然是 0。因为棋子总数每回合递减,终局(全 0,异或和为 0)必定由后手撞上。后手必败,先手必胜。
如果起手 \(S = 0\):
先手处于被动方,无论怎么走都会把 \(S \neq 0\) 送给后手,后手接管必胜策略。先手必败。
- 代码实现 (C++)
由于每一组数据的判断仅需计算所有数的异或和,代码实现极其精简。
#include <iostream>
using namespace std;
void solve() {
int n;
cin >> n;
int xor_sum = 0;
for (int i = 0; i < n; ++i) {
int a;
cin >> a;
xor_sum ^= a; // 依次异或每一堆的数量
}
// 如果异或和不为 0,先手必胜;否则先手必败
if (xor_sum != 0) {
cout << "Win\n";
} else {
cout << "Lose\n";
}
}
int main() {
// 提高输入输出效率
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
复杂度分析
时间复杂度:对于每组数据,仅需遍历一次长度为 \(N\) 的数组并执行异或操作,单组复杂度为 \(\mathcal{O}(N)\)。总时间复杂度为 \(\mathcal{O}(\sum N)\),能在 1.0 秒内轻松通过规模 \(10^5\) 级别的数据。
空间复杂度:仅使用常数个辅助变量维护状态,空间复杂度为 \(\mathcal{O}(1)\)。

浙公网安备 33010602011771号