参考博客:https://baike.baidu.com/item/SG%E5%87%BD%E6%95%B0/1004609
https://www.cnblogs.com/ECJTUACM-873284962/p/6921829.html
主要参考百度百科:
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{1,3,5}=0、mex{}=0。
对于任意状态 x , 定义 SG(x) = mex(S),其中 S 是 x 后继状态的SG函数值的集合。如 x 有三个后继状态分别为 SG(a),SG(b),SG(c),那么SG(x) = mex{SG(a),SG(b),SG(c)}。 这样 集合S 的终态必然是空集,所以SG函数的终态为 SG(x) = 0,当且仅当 x 为必败点P时。
SG函数的性质:
首先,所有的terminal position(目标位置)所对应的顶点,也就是没有出边的顶点,其SG值为0,因为它的后继集合是空集。然后对于一个g(x)=0的顶点x,它的所有前驱y都满足 g(y)!=0。对于一个g(x)!=0的顶点,必定存在一个后继y满足g(y)=0。
SG值的意义:
1,当g(x)=k时,表明对于任意一个0<=i<k,都存在x的一个后继y满足g(y)=i。为什么一定存在呢?
因为mex函数运算时,g(y)等于最小的不属于(y后继状态)这个集合的非负整数,换言之,y的后继状态一定有 [0,k-1] 这个范围,才会出现g(x)=k,k为不小于后继状态(集合),因为函数g就是按照mex运算得来的。
2,当某枚棋子的SG值是k时,我们可以把它变成0、变成1、……、变成k-1,但绝对不能保持k不变。这个联想到Nim游戏, Nim游戏的规则就是:每次选择一堆数量为k的石子,可以把它变成0、变成1、……、变成k-1,但绝对不能保持k不变。这表明,如果将n枚棋子所在的顶 点的SG值看作n堆相应数量的石子,那么这个Nim游戏的每个必胜策略都对应于原来这n枚棋子的必胜策略!
3,对于n个棋子,设它们对应的顶点的SG值分别为(a1,a2,…,an),再设局面(a1,a2,…,an)时的Nim游戏的一种必胜策略是把ai 变成k,那么原游戏的一种必胜策略就是把第i枚棋子移动到一个SG值为k的顶点。这就相当于可以看做,每次最聪明的操作是按照Nim游戏SG(N)值来移动,例如我们想把SG(N)变为SG(小于N),就满足最聪明移动,但我们真正移动的又不是SG(N)的值,移动的是原来顶点N的值,咦,好像也是哦,没关系,我们依旧移顶点N的值,只是说移动顶点N的值时,要满足移动后顶点值(小于N),SG(小于N)的值是我们想要的最聪明的SG值。
这就好像我们敲代码,我们通过高级程序语言来间接写机器语言运行计算机。
计算1~n的SG函数值步骤如下:
1、使用 数组f 将 可改变当前状态 的方式记录下来。
2、然后我们使用 另一个数组 将当前状态x 的后继状态标记。
3、最后模拟mex运算,也就是我们在标记值中 搜索 未被标记值 的最小值,将其赋值给SG(x)。
4、我们不断的重复 2 - 3 的步骤,就完成了 计算1~n 的函数值
代码:
//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合
int f[N],SG[MAXN],S[MAXN];
void getSG(int n){
int i,j;
memset(SG,0,sizeof(SG));
//因为SG[0]始终等于0,所以i从1开始
for(i = 1; i <= n; i++){
//每一次都要将上一状态 的 后继集合 重置
memset(S,0,sizeof(S));
for(j = 0; f[j] <= i && j <= N; j++)
S[SG[i-f[j]]] = 1; //将后继状态的SG函数值进行标记
for(j = 0;; j++) if(!S[j]){ //查询当前后继状态SG值中最小的非零值
SG[i] = j;
break;
}
}
}
给出一道入门级的题目:
hdu 1848
#include
#include
#include
using namespace std;
const int MAXN=1010;
#define N 20
int f[N],SG[MAXN],S[MAXN];///f[]可走步数//S[]后继存在状态
void getSG(int n){
int i,j;
memset(SG,0,sizeof(SG));///PN点
for(i = 1; i <= n; i++){///从i=1开始
memset(S,0,sizeof(S));///每次初始化S[]后继存在状态
for(j = 0; f[j] <= i && j < N; j++)///f[i]
我的标签:做个有情怀的程序员。