sg函数(个人认为还是用于三种方法都无法解决的情况,如按特殊数字取石子)
我们把整个博弈过程抽象为有向无环图
1. 几项准备工作:
mex求最小非负整数mex{} = 0,mex{0,1,2,4} = 3,mex{1,2,4} = 0
sg[x] =mex{sg[y]|y是x的后继}//就是石头变少的继
这样sg就满足几个性质
1. sg[x] == 0时,它的后继都不为零
2. sg[x] != 0时,它的后继一定有为零的
3. 当x点没有出边时,sg[x] == 0
这三个性质恰好与P-positon(先手必败)的性质相同:
(1).无法进行任何移动的局面(也就是terminal position)是P-position;
(2).可以移动到P-position的局面是N-position;
(3).所有移动都导致N-position的局面是P-position。
由此可知:sg[x] == 0,x就是p-position
2.
对于从一堆n个石块中取石块的过程,每次取法有一定特色(比如说按照菲薄纳切数列来去)只需求出sg[x]就可以判断了
对于从m堆石块中取石块的过程,每次取法是特殊的。只需将所有s[n]亦或就是结果
让我们再来考虑一下顶点的SG值的意义。当g(x)=k时,表明对于任意一个0<=i<k,都存在x的一个后继y满足g(y)=i。也就是说,当某枚棋子的SG值是k时,我们可以把它变成0、变成1、……、变成k-1,但绝对不能保持k不变。不知道你能不能根据这个联想到Nim游戏,Nim游戏的规则就是:每次选择一堆数量为k的石子,可以把它变成0、变成1、……、变成k-1,但绝对不能保持k不变。这表明,如果将n枚棋子所在的顶点的SG值看作n堆相应数量的石子,那么这个Nim游戏的每个必胜策略都对应于原来这n枚棋子的必胜策略!
假设一堆石块有n个石块这就意味着sg[n]确实等价为从n个石块中每次至少取一个石头
4.模板.注意要根据题目的要求初始化a[i]。切记初始化sg都为-1,init中的a一定是从小到大的
实践证明暴搜比打表快。因为暴搜得到的值,不用也不应该清空。下一次可以根据上一次暴搜得到的值进行处理
1.dfs递归版。从n个石头开始递归
调用方式:SG(n)
<pre name="code" class="cpp">const int MAXN=1005;
int a[MAXN],sg[MAXN];
void init()
{
a[1] = 1,a[2] = 2;
for(int i = 3; i < 20; i ++)
{
a[i] = a[i - 1] + a[i - 2];
// cout<<a[i]<<endl;
}
}
int SG(int x)
{
bool vis[105] = {false};
int temp;
for(int i = 0; i <n && a[i]<= x; i ++)//n是i的个数
{
temp= x - a[i];
if(sg[temp]== -1)
{
sg[temp] = SG(temp);
}
vis[sg[temp]]= true;
}
for(int i = 0;; i ++)
{
if(vis[i]== false)
{
return i;
}
}
}
2. 打表法
调用方法:sg[n]
const int MAXN=1005;
int a[MAXN],sg[MAXN],b[MAXN];
int n,maxx;//maxx表示sg[]表的大小。n表示的是a[]的大小,也就是每一步所能走的值的集合的大小
void init()
{
a[1] = 1,a[2] = 2;
for(int i = 3; i < 20; i ++)
{
a[i] = a[i - 1] + a[i - 2];
// cout<<a[i]<<endl;
}
}
void SG()
{
for(int i = 0; i <= maxx; i ++)
{
memset(b,true,sizeof(b));
for(int j = 0; j < n; j ++)
{
if(i < a[j])
break;
b[sg[i - a[j]]] = false;//不是i - a[j]是sg[i-a[j]]
}
for(int j = 0; j <= maxx; j ++)
{
if(b[j])
{
sg[i] = j;
break;
}
}
}
}