SG函数是用于解决博弈论中公平组合游戏(Impartial Combinatorial Games,ICG)问题的一种方法。
所谓ICG,应当满足以下几条性质:
- 有两名玩家
- 两名玩家轮流操作,在一个有限集合内任选一个进行操作,改变游戏当前局面
- 一个局面的合法操作,只取决于游戏局面本身且固定存在,与玩家次序或者任何其它因素无关
- 无法操作者,即操作集合为空,输掉游戏,另一方获胜[1]
最简单的ICG当然就是经典的Nim游戏了:
地上有 n 堆石子,每堆石子数量可能不同,两人轮流操作,每人每次可从任意一堆石子里取走任意多枚石子,可以取完,不能不取,无石子可取者输掉游戏,问是否存在先手必胜的策略。
之后我们会多次以Nim游戏举例。
对于ICG,我们定义一个函数 sg(x) ,令 sg(x):=mex({sg(y)|x→y}) 。其中, x 和 y 都表示某种状态, x→y 在这里表示 x 状态可以通过一次操作达到 y 状态, mex 表示一个集合中未出现的最小自然数(例如 mex({0,1,3})=2 ,经常打cf的人应该对这个函数比较熟悉)。如果 sg(x)=n ,说明从当前状态可以转移到 sg 为 0,1,…,n−1 的状态。
我们称令 sg(x)=0 的状态 x 为必败态。显然,如果 x 是无法操作的状态(即 {sg(y)|x→y}=∅ ,在Nim游戏中是没有石子可取的状态),则必有 sg(x)=0 ,这时当前玩家输掉游戏;若不然,则该状态只能转移到 sg 不为 0 的状态,那么对手立刻又可以转移到 sg 为 0 的状态,这样进行有限次后一定会陷入无法操作的状态输掉比赛。
相反,如果 sg(x)≠0 ,则称 x 为必胜态,说明此时存在策略使自己必定取胜(即每次轮到自己都转移到 sg 为 0 的状态)。
对于单堆的Nim游戏,我们很容易计算其 sg 值。设 sg(m) 表示剩余石子数为 m 的状态的 sg 值,则显然 sg(0)=0 ,那么 sg(1)=1 ,sg(2)=2 ……归纳可证 sg(n)=n 。因此,石子数不为0都是必胜态。
对于双堆的Nim游戏,设 sg(n,m) 表示剩余石子数分别为 n 和 m 的状态的 sg 值,则 sg(0,0)=0 ,并且 sg(n,0)=sg(0,n)=n 。我们发现 sg(1,1)=0 ,而 sg(1,2)=sg(2,1)=1 ,sg(2,2)=0 ……继续探索可以猜测,当 n=m 时,sg(n,m)=0 ,否则 sg(n,m)≠0 。这可以归纳证明。
当堆数越来越多时,这样盲目探索显得很困难。所幸,我们有SG定理。
定义若干个ICG的组合为这样一个游戏:每位玩家每个回合在这若干个ICG(称为子游戏)中选择一个,对它进行一次操作;当所有子游戏都无法进行操作时,当前玩家输掉游戏。记两个ICG的状态分别为 x 和 y ,则记这两个ICG的组合的状态为 x+y 。
Sprague-Grundy定理(两个游戏的情形):
设 x 和 y 为ICG的状态,则: sg(x+y)=sg(x)⊕sg(y)
其中 ⊕ 表示异或。
这个定理还是很神奇的,这里就不展开篇幅证明了,这里有很多证明。可以这样感性地理解:如果两个子游戏的 sg 值异或不为 0 ,那么一定可以转移到一个 sg 值异或和为 0 的状态(将 sg 值较大者转移到与较小者的 sg 值相同的状态就可以了,这一定能做到);相反,如果两个子游戏的 sg 值异或为 0,则只能转移到 sg 值异或和不为 0 的状态——这正好对应了必胜态和必败态。
这个定理很容易进行推广:
设 x1,x2,…,xn 为ICG的状态,则: sg(x1+x2+⋯+xn)=sg(x1)⊕sg(x2)⊕⋯⊕sg(xn)
利用SG定理,我们就可以轻松地把单一游戏的情况推广到多个子游戏组合的情况。例如Nim游戏,就是在 n 堆石子的异或和为 0 时必败,否则必胜。
举一个例题:
小 E 与小 W 进行一项名为E&D
游戏。
游戏的规则如下:桌子上有 2n 堆石子,编号为 1∼2n 。其中,为了方便起见,我们将第 2k−1 堆与第 2k 堆( 1≤k≤n )视为同一组。第 i 堆的石子个数用一个正整数 Si 表示。
一次分割操作指的是,从桌子上任取一堆石子,将其移走。然后分割它同一组的另一堆石子,从中取出若干个石子放在被移走的位置,组成新的一堆。操作完成后,所有堆的石子数必须保证大于 0 。显然,被分割的一堆的石子数至少要为 2 。两个人轮流进行分割操作。如果轮到某人进行操作时,所有堆的石子数均为 1 ,则此时没有石子可以操作,判此人输掉比赛。
小 E 进行第一次分割。他想知道,是否存在某种策略使得他一定能战胜小 W。因此,他求助于小 F,也就是你,请你告诉他是否存在必胜策略。例如,假设初始时桌子上有 4 堆石子,数量分别为 1,2,3,1 。小 E 可以选择移走第 1 堆,然后将第 2 堆分割(只能分出 1 个石子)。接下来,小 W 只能选择移走第 4 堆,然后将第 3 堆分割为 1 和 2 。最后轮到小 E,他只能移走后两堆中数量为 1 的一堆,将另一堆分割为 1 和 1 。这样,轮到小 W 时,所有堆的数量均为 1 ,则他输掉了比赛。故小 E 存在必胜策略。
很显然这是一个ICG,且可以看作 n 个子游戏的组合,显然每一组两堆石子组成最小单位。我们直接打表列出 sg 值(按照定义计算,可以写程序或手算):
这张表显然很有规律,例如当 a 和 b 是奇数时 s(a,b)=0 ,还有 sg(i,j)=sg(j,i) 等等。还很容易发现,偶数列(或行)都是几个几个一组的。在此基础上进一步观察,可以发现第 2n 列的值与第 n 列是有关联的。
总结成公式就是:当 a 为偶数时, sg(a,b)=sg(a2,⌊b−12⌋+1)+1 。于是可以递归求解,代码如下:
#include <iostream>
using namespace std;
using ll = long long;
int count(ll n)
{
int cnt = 0;
while (n % 2 == 0)
n /= 2, cnt++;
return cnt;
}
int sg(ll a, ll b)
{
if (a % 2 && b % 2)
return 0;
else if (a % 2 == 0)
return sg(a / 2, (b - 1) / 2 + 1) + 1;
else
return sg(b, a);
}
int main()
{
ll t, n, x, y;
cin >> t;
while (t--)
{
int res = 0;
cin >> n;
for (int i = 0; i < n / 2; ++i)
{
cin >> x >> y;
res ^= sg(x, y);
}
cout << (res ? "YES" : "NO") << endl;
}
return 0;
}
我们并没有严格地证明观察出结论,但这就是这类题的通常做法:打表找规律
int mex(auto v) // v可以是vector、set等容器
{
unordered_set<int> S;
for (auto e : v)
S.insert(e);
for (int i = 0;; ++i)
if (S.find(i) == S.end())
return i;
}