公平组合游戏中,最基础也最重要的是正常 Nim 游戏。Sprague–Grundy 定理指出,所有正常规则的公平组合游戏都等价于一个单堆 Nim 游戏。由此,可以发展出 Sprague–Grundy 函数和 Nim 数的概念,它们完全地刻画了一个正常规则的公平组合游戏。因此,本文首先建立了正常 Nim 游戏的结论和 Sprague–Grundy 理论。随后,本文讨论了算法竞赛中常见的一些公平组合游戏。
最后,本文简单地讨论了反常 Nim 游戏。反常游戏相对于正常游戏来说要复杂得多,也很少在算法竞赛中出现。本文提到的游戏,如果没有特别说明,均默认为正常的公平组合游戏。
如果 ai=0 对所有 i=1,⋯,n 都成立,该状态没有后继状态,且 Nim 和等于 0,命题成立。
如果 k=a1⊕a2⊕⋯⊕an=0,那么,需要证明该状态是必胜状态。也就是说,需要构造一个合法移动,使得后继状态为必败状态;由归纳假设,只需要证明后继状态满足 a1′⊕a2′⊕⋯⊕an′=0。利用 Nim 和(即异或)的性质,这等价于说,存在一堆石子,将 ai 拿走若干颗石子,可以得到 ai⊕k,亦即 ai>ai⊕k。
实际上,设 k 的二进制表示中,最高位的 1 是第 d 位。那么,一定存在某个 ai,使得它的二进制第 d 位是 1。对于相应的石子堆,就一定有 ai>ai⊕k,因为 ai⊕k 中第 d 位为 0,更高位和 ai 一样。
如果 a1⊕a2⊕⋯⊕an=0,那么,需要证明该状态是必败状态。由归纳假设可知,只要证明它的所有后继状态的 Nim 和都不是 0。这是必然的,任何合法移动将 ai 变为 ai′=ai,就必然会使得 Nim 和变为 ai′⊕ai=0。
由此,可以在 O(n) 时间内判断 Nim 游戏的一个状态是否为先手必胜状态。
Sprague–Grundy 理论
Sprague–Grundy 理论指出,所有公平组合游戏都等价于单堆 Nim 游戏。这一结论主要应用的场景,就是游戏由多个相互独立的子游戏组成的情形。此时,游戏的状态判定可以通过计算子游戏的 SG 函数值的 Nim 和来完成。如果游戏本身没有这样的结构,那么,判定必胜状态和必败状态只需要应用前文博弈图一节的 引理。
游戏的记法
前文已经说明,所有公平组合游戏都可以通过绘制博弈图来描述。由于博弈图中,每个状态的性质只由它的后继状态决定,所以,可以将博弈图中的一个状态 S 用它的后继状态的集合来表示。
游戏 G 和 H 的 和(sum),或称 游戏组合(combined game),记作 G+H,是指游戏
G+H={g+H:g∈G}∪{G+h:h∈H}.
游戏的和,可以理解为由两个同时进行且互不干扰的子游戏组成的游戏,玩家在每一步能且只能选择其中一个子游戏移动一步,且游戏在两个子游戏都无法移动时结束。游戏的和的概念,可以推广到任意多个游戏的情形,且满足结合律和交换律——也就是说,多个游戏组合的结果,和组合进行的次序以及游戏的顺序都无关。Nim 游戏就是多个单堆 Nim 游戏的和。
一个观察是,尽管单堆 Nim 游戏中,除了没有石子的情形,都是先手必胜状态,但是这些不同的单堆 Nim 游戏在和其他的单堆 Nim 游戏组合起来时,得到的游戏并不相同。比如,游戏 ∗n 只有在和另一个 ∗n 组合时,才能得到一个必败游戏;和所有其他的游戏 ∗n′=∗n 组合,得到的游戏都是必胜游戏。
共有 n 堆石子,第 i 堆有 ai 枚石子。两名玩家轮流取走至少 1 堆、至多 k 堆中的任意多枚石子,但不能不取。取走最后一枚石子的玩家获胜。
对此,有如下结论:
定理
将每一堆石子的数目都表示为二进制数,并对每个数位 d,都统计有多少堆石子数目的第 d 位是 1,并计算这个数目对于 (k+1) 的余数。如果对于每个数位,这个余数都等于 0,那么先手必败;否则,先手必胜。
证明
仿照 Nim 游戏的结论的证明,很容易证明本结论。设 d 为余数不为 0 的最高二进制位,且对应的余数为 k′≤k。那么,必胜策略为,在石子数目二进制第 d 位为 1 的石子堆中,选择 k 堆,并选择移走的石子数目恰好使得对手局面中,每个数位的余数都是 0。唯一需要说明的是,最后取走石子数量的选择总是可行的。
实际上,只要选定 k′ 堆石子,每堆都取走 2d 枚石子,就能使得结果中,第 d 位余数变为 0。对于更低的数位的余数,将这些余数随意摊派给某一个堆即可。
阶梯 Nim 游戏
阶梯 Nim 游戏稍微复杂一些,它允许石子在相邻的堆之间移动。
阶梯 Nim 游戏
共有 n 堆石子,第 i 堆有 ai 枚石子。两名玩家轮流操作,每次操作中,要么取走第 1 堆石子中的任意多枚,要么将第 i>1 堆石子中的任意多枚移动到第 i−1 堆,但不能不做任何操作。取走最后一枚石子的玩家取胜。
对此,有如下结论:
定理
游戏先手必败,当且仅当奇数堆石子数量的 Nim 和 a1⊕a3⊕⋯⊕an−1+(nmod2)=0。
证明
任何玩家将偶数堆的石子移动到奇数堆时,对手都可以将这些石子继续移动到下一个偶数堆(或移走),因此,这样的移动不会影响奇数堆的局面。此时,每一个奇数堆向下移动到相邻的偶数堆(或移走)都可以看作独立的单堆 Nim 游戏。根据 Sprague–Grundy 定理关于游戏的和的结论,阶梯 Nim 游戏的 SG 函数值,是这些子游戏的 SG 函数值的 Nim 和。这就得到上述结论。
Fibonacci Nim 游戏
Fibonacci Nim 游戏类似 Bachet 游戏,只有一堆石子,且限制了每次取走的数量。与 Bachet 游戏不同,Fibonacci Nim 游戏中,每次取走的数量的限制是动态的。
Fibonacci Nim 游戏
有一堆石子,共计 n 枚。两名玩家轮流取石子。第一个行动的玩家不限制取走的石子数目,但是不能取完石子;随后,每次取走的石子数目不得超过上次(指对手回合)取走的石子数目的二倍。每次取走的石子的数目不得为 0。取走最后一枚石子的玩家获胜。
两个玩家轮流行动。每个玩家面临的局面都由一个无向图 G=(V,E) 和它的一个顶点 v∈V 构成。在一名玩家的回合中,若当前局面为 (G,v),则该玩家必须选择一个与 v 相邻的顶点 u。随后,将顶点 v 及其所有关联边从图 G 中删除,得到残余图 G′。新的局面即为 (G′,u),交由下一位玩家。若某位玩家在其回合开始时,当前顶点 v 在图中没有相邻顶点(即不存在合法选择),则该玩家无法行动,并因此输掉游戏。
对此,有如下结论:
定理
游戏先手必胜,当且仅当顶点 v 是图 G 的最大匹配关键点,也就是说,在图 G 的所有最大匹配中,顶点 v 都是匹配点。
证明
首先,顶点 v 是图 G 的最大匹配关键点。设 G 的一个最大匹配为 M。此时,先手可以将局面移动到在 M 中与顶点 v 匹配的顶点 u。由于顶点 v 出现在所有图 G 的最大匹配中,所以,残余图 G′ 的最大匹配的大小至多是 ∣M∣−1;而且将 M 去掉边 (v,v) 就能得到图 G′ 的一个大小为 ∣M∣−1 的匹配 M′:结合这两点就知道,M′ 是图 G′ 的一个最大匹配。但是,后手玩家所处的局面中,顶点 u 并不是匹配 M′ 的一个匹配点。因此,后手玩家必然处于一个必败状态。
反过来,假设存在最大匹配 M 使得 v 是未匹配点。由于 M 是最大匹配,与顶点 v 相邻的顶点一定是匹配点;否则,就可以将它们之间的连边添加到 M 中,得到一个更大的匹配。因此,无论先手怎么选择,后手都处于一个必胜状态。
情形 A:如果只有一堆石子的数量严格大于 1,那么,此时 Nim 和一定不为 0。而且,由于先手玩家可以选择转移到全部堆的石子数量均不超过 1 的局面,而且可以控制剩余的非空石子堆的奇偶性。因此,此时为先手必胜态 N。
情形 B:现在,有不止一堆石子的数量严格大于 1,那么,无论怎么操作,下一个局面中,都至少有一堆石子的数量严格大于 1。根据归纳假设,下一局面中,先手必败对应着 Nim 和为零,先手必胜对应着 Nim 和不为零。这与正常 Nim 游戏的归纳假设完全相同。因此,重复 Nim 游戏的论证,就能知道,当前局面同样符合 Nim 和为零对应先手必败状态的结论。
有 n 堆石子,第 i 堆有 ai 枚。两人玩 Nim 游戏。现在,可以任意指定若干堆石子作为初始局面,并指定其中一堆石子要求先手玩家首轮必须从中取走石子,但不能指定取走石子的数目。问有多少种指定方式,使得先手无法获得胜利。数据满足 n,ai≤200。
解答
对于这类问题,需要利用常见游戏的结论,并结合其他部分知识来进行解答。假设指定先手必须取走第 i 堆石子,且指定的所有石子堆数量 Nim 和为 v,那么,先手无法获得胜利,当且仅当 ai≤ai⊕v,也就是说,第 i 堆石子数量 ai 不超过除第 i 堆外剩余石子堆数量 Nim 和 ai⊕v。由于数据范围很小,直接枚举指定首轮取石子的堆;枚举到第 i 堆时,剩余每个堆选或不选,可以得到不同 Nim 和的方案数可以通过 DP 计算出来,将最后得到的方案数中大于等于 ai 的部分加总起来即可。
参考代码
#include <array>#include <iostream>#include <vector>int main() { constexpr int M = 1e9 + 7; constexpr int L = 256; int n; std::cin >> n; std::vector<int> a(n); for (auto& x : a) std::cin >> x; int res = 0; for (int i = 0; i < n; ++i) { std::array<int, L> dp = {}; dp[0] = 1; for (int j = 0; j < n; ++j) { if (j == i) continue; std::array<int, L> _dp = {}; for (int v = 0; v < L; ++v) { _dp[v] = (dp[v] + dp[v ^ a[j]]) % M; } dp = std::move(_dp); } for (int v = a[i]; v < L; ++v) { res = (res + dp[v]) % M; } } std::cout << res << std::endl; return 0;}
#include <array>#include <iostream>#include <vector>int main() { // Transition helper as explained above. auto calc = [](int x0, int y0, int x) -> int { return x == x0 ? 0 : ((x < x0 && x < y0) || (x > x0 && x > y0) ? x : (x0 < y0 ? x - 1 : x + 1)); }; int t; std::cin >> t; for (; t; --t) { int n; std::cin >> n; std::vector<int> a(n); for (auto& x : a) std::cin >> x; // dp[i][j][0] = the unique value x such that // f_{i-1,j}(x, a_j) = 0 // i.e. the interval game [i, j] is in a P-position // when a pile of size x is attached to the LEFT. // // dp[i][j][1] = the unique value y such that // f_{i,j+1}(a_i, y) = 0 // i.e. the interval game [i, j] is in a P-position // when a pile of size y is attached to the RIGHT. std::vector<std::vector<std::array<int, 2>>> dp( n, std::vector<std::array<int, 2>>(n)); // Base case: single-element interval [i, i]. // The "left" and "right" pile values both equal a[i]. for (int i = 0; i < n; ++i) dp[i][i][0] = dp[i][i][1] = a[i]; // Build up intervals of increasing length d. for (int d = 1; d < n; ++d) { for (int i = 0; i + d < n; ++i) { dp[i][i + d][0] = calc(dp[i][i + d - 1][1], dp[i][i + d - 1][0], a[i + d]); dp[i][i + d][1] = calc(dp[i + 1][i + d][0], dp[i + 1][i + d][1], a[i]); } } // The original game corresponds to attaching nothing on the left. // It is a P-position if and only if the unique value y satisfying // f_{-1, n-1}(0, y) = 0 // is y = 0. std::cout << (dp[0][n - 1][0] != 0) << '\n'; } return 0;}
Conway, John H. On numbers and games. AK Peters/CRC Press, 2000.
Berlekamp, Elwyn R., John H. Conway, and Richard K. Guy. Winning ways for your mathematical plays, volume 1-4. AK Peters/CRC Press, 2001-2004.
Footnotes
「N 态」和「P 态」这两个名称分别表示「下一名玩家胜利」(Next player wins)和「前一名玩家胜利」(Previous player wins)。 ↩
本文讨论的「和」都是 长规则(long rule)下的 析取和(disjunctive sum)。这也是最常见的一种游戏组合方式。除此之外,还有其他可能的游戏组合方式。关于它们的详细讨论,可以参考 Conway, John H. On numbers and games. AK Peters/CRC Press, 2000. 一书的第 14 章。 ↩