博弈游戏
sg函数
适合范围
1,2人游戏
2,每次操作合法有限
3,两人绝对聪明
4,如果当前选手无法进行合法的操作,则为负
定义状态
P(pre player)点:必败点,换而言之,就是谁处于此位置,则在双方操作正确的情况下必败。
N(next player)点:必胜点,处于此情况下,双方操作均正确的情况下必胜。
sg[0]=0 是必败点
sg[n]==0 则为必败点 若sg[n]>0 则为必胜点
sg函数
mex(minimal excludant)运算:
不包含在集合mex中,最小的整数:
mex{0,1,2,3,5,7}=4
mex{1,3,5,6}=0
函数
设***S***为当前n个石子,可转移到的状态的集合,
sg(n)=mex(sg[x1],sg[x2]……sg[xn]),xi∈***S***
sg[n]==0 则为必败点 若sg[n]>0 则为必胜点
Nim和
游戏的sg函数等于各个子游戏的Nim和
原理:
1,单个游戏
如果先手胜利:
说明先手每次都可以将当前局面通过某一种方式到达后手必败的局面。
而后手不论如何操作,进入的状态都是不确定的局面,但在这种局面内,下一个人可以通过某一操作,又进入必败态。
如果,线代表操作,而点代表状态,进入必败态后,下一个操作一定进入必胜态。进入必胜态之后,有多种可能的操作,但其中,至少有一条线连入必败态。
举例:
有1堆n个的石子,每次只能取{ 1, 3, 4 }个石子,先取完石子者胜利,
画图:
sg函数>0的点可以通过一种方式,抵达sg0的状态,sg0的状态无论怎么动,都只能进入sg>0的胜态
6 先手:4
2 后手:1
1先手:1胜利
多个游戏:
游戏的sg等于各个子游戏的NIm和
sg[all]=sg[x1]^ sg[x2] ^sg[x3] …… ^sg[xn]
核心sg[k]>0 则有一种状态进入sg[j]==0 但sg[j]==0 不可能进入sg[k]>0
如果sg[all]=k!=0
则二进制表示中一定有一个1 ,并且这个1 是属于某一个sg[xi]的最高位(sg[xi]肯定不等于0),这时候,
令y=k^sg[xi] y肯定是小于k和 sg[xi] 的,因为最高位被消没了
根据sg函数的定义,那么sg[xi]就可以通过某一步变为sg[xi]==y
这时候sg[all]=sg[x1]^ sg[x2] ^sg[x3] …… ^sg[xn]=k ^ k^sg[xi]==0
而不论后手怎么调整sg[xi] 都必然是sg[all]>0 除非没有石子可以取。
代码:
//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;
}
}
}
) if(!S[j]){ //查询当前后继状态SG值中最小的非零值
SG[i] = j;
break;
}
}
}