SG函数总结
基本概念
首先定义 \(mex\) (minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如 \(mex\{0,1,2,4\}=3\) 、\(mex\{2,3,5\}=0\)、\(mex\{\}=0\) 。
对于任意状态 \(x\) ,定义 \(SG(x) = mex(F)\) ,其中 \(F\) 是 \(x\) 后继状态的 \(SG\) 函数值的集合(就是上述 \(mex\) 中的数值)。例如 \(x\) 有三个后继状态分别为 \(SG(a)\) 、\(SG(b)\) 、\(SG(c)\) ,那么 \(SG(x) = mex\{SG(a),SG(b),SG(c)\}\) 。当且仅当 \(x\) 为必败点时,\(SG(x)=0\) 。
游戏和的 \(SG\) 函数等于各个游戏 \(SG\) 函数的 \(Nim\) 和。
取石子问题
有 \(1\) 堆 \(n\) 个石子,每次操作只能取 \(\{ 1, 3, 4\}\) 个石子,先取完石子者胜利,那么各个数的 \(SG\) 值为多少?
初始:\(SG[0] = 0\),\(,f[]=\{1,3,4\}\) 。
\(x=1\) 时:可以取走 \(1 - f\{1\}\) 个石子,剩余 \(\{0\}\) 个,所以 \(SG[1] = mex\{ SG[0] \}= mex\{0\} = 1\) ;
\(x=2\) 时:可以取走 \(2 - f\{1\}\) 个石子,剩余 \(\{1\}\) 个,所以 \(SG[2] = mex\{ SG[1] \}= mex\{1\} = 0\) ;
\(x=3\) 时:可以取走 \(3 - f\{1,3\}\) 个石子,剩余 \(\{2,0\}\) 个,所以 \(SG[3] = mex\{SG[2],SG[0]\} = mex\{0,0\} =1\) ;
\(x=4\) 时:可以取走 \(4- f\{1,3,4\}\) 个石子,剩余 \(\{3,1,0\}\) 个,所以 \(SG[4] = mex\{SG[3],SG[1],SG[0]\} = mex\{1,1,0\} = 2\) ;
\(x=5\) 时:可以取走 \(5 - f\{1,3,4\}\) 个石子,剩余 \(\{4,2,1\}\) 个,所以 \(SG[5] = mex\{SG[4],SG[2],SG[1]\} =mex\{2,0,1\} = 3\) ;
以此类推。
SG函数打表模板
//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;
}
}
}