推sg值

1.前言:为什么我们需要 SG 函数?
以前做博弈论的题,总是想着背公式:巴什博弈、威佐夫博弈、斐波那契博弈……背得头都大了。
但在实际比赛(ICPC/CCPC)中,出题人往往会修改规则,比如:“每次只能取 1, 3, 4 个石子”。这时候,所有的公式全部失效。
其实,博弈论的本质不是数学公式,而是 状态图上的搜索(DAG)。
掌握了 SG 函数 (Sprague - Grundy) 和 打表找规律 的方法,不管规则怎么变,都能一招鲜吃遍天。
2.核心概念:什么是 Mex 与 SG?
要理解 SG 函数,先记住两个概念:
Mex (Minimum Excluded value):
“缺席的最小自然数”。
在一个非负整数集合中,没出现的最小自然数。
Mex {1, 2} = 0 (0 没出现)
Mex {0, 1, 3} = 2 (2 没出现)
Mex {0, 1, 2, 3} = 4
Mex {} = 0 (空集缺席的最小自然数是 0)
SG 函数的递归定义:
对于任意状态 u,它的 SG 值等于 它所有能到达的后继状态的 SG 值的 Mex。
SG(u)=Mex{SG(v1),SG(v2),…,SG(vk)}
必败态 (P - position):
SG(u)=0。
必胜态 (N - position):
SG(u)≠0。
3.终极武器:SG 定理 (Sprague - Grundy Theorem)
如果是多堆石子(或者多个独立的游戏组合),怎么办?
定理:整个局面的 SG 值,等于各个子游戏 SG 值的 异或和 (XOR, ⊕)。
SGtotal=SG(x)⊕SG(y)⊕SG(z)…
如果 SGtotal≠0 ⟹ 先手必胜。
如果 SGtotal=0 ⟹ 先手必败。
人话总结:别管有多少堆,把每一堆看成独立的,算出它们的 SG 值(也就是“等级”),然后异或起来。如果不为 0,你就赢了。
4.实战核武器:暴力打表 + 找规律
遇到复杂的规则(例如:每次取 1,3,4 个),千万别手推,直接上代码打表!
通用步骤:
写一个 记忆化搜索 (DFS) 的 get_sg(n) 函数。
利用 set 记录后继状态的 SG 值。
跑循环打印前 100 项 SG 值。
肉眼观察 输出序列,寻找 循环节(通常是 6, 8, 12 这种长度)。
提交代码时,直接用 SG[n % 周期] 解决 N 很大的情况。
5.通用代码模板 (C++ Version)
这是我总结的防坑模板,注意 n - k >= 0 的判断!

`#include <bits/stdc++.h>
using namespace std;

const int MAX_N = 1005; // 打表范围,通常100-200就够看规律了
int memo[MAX_N]; // 记忆化数组

// 假设规则:每次可以取 {s1, s2, s3...} 个石子
// 以每次取 1, 3, 4 个为例
int S[] = {1, 3, 4};
int k = 3; // 规则数组的长度

int get_sg(int n) {
if (n == 0) return 0; // 递归边界:0个石子必败
if (memo[n] != -1) return memo[n]; // 记忆化

set<int> visited; // 记录所有后继状态的 SG 值

// 遍历所有合法的操作
for (int i = 0; i < k; i++) {
    if (n - S[i] >= 0) { // 【关键】必须 >= 0,0也是一种合法的后继状态
        visited.insert(get_sg(n - S[i]));
    }
}

// 计算 Mex (缺席的最小自然数)
int mex = 0;
while (visited.count(mex)) {
    mex++;
}

return memo[n] = mex;

}

int main() {
// 初始化记忆化数组
memset(memo, -1, sizeof(memo));

// 打表打印前 50 项
for (int i = 0; i <= 50; i++) {
    printf("SG[%d] = %d\n", i, get_sg(i));
}

// 观察输出:  
// 0, 1, 0, 1, 2, 3, 2, 
// 0, 1, 0, 1, 2, 3, 2, ...
// 发现了吗?每 7 个数是一个循环!
// 结论:SG(n) = SG(n % 7)

return 0;

}
`
6.避坑指南(血泪史)
后继判断:一定要写 if (n - x >= 0)!我之前写成了 > 0,导致漏掉了转移到 0 状态的情况,算出来的全是错的。
Mex 写法:用 while(s.count(mex)) mex++; 是最稳的,别搞花里胡哨的循环。
Nim 博弈 vs SG:
标准的 Nim(随便取):
SG(n)=n。
变种 Nim(限制取法):
SG(n) 需要打表算。
最后都是 异或 解决。
7.总结
博弈论的题,看似是数学题,实则是 搜索 + 找规律 的题。
只要写出这个 get_sg 模板,就没有解不开的公平组合游戏。
以后看到 N 很大、规则很怪的博弈题,先打表,再说话!

posted @ 2025-12-08 10:27  leeezhusl  阅读(24)  评论(0)    收藏  举报