Nim博弈
题目:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时,所有的石子堆都已经被拿空了,则判负。
首先介绍xor运算。xor满足交换律,结合律,消去律,并且与自己本身互为逆运算。根据这个性质可以将swap函数写为
void swap(int &a, int &b) { a=a^b; b=a^b; a=a^b; }
类似Nim博弈的问题,最重要的是要寻求“必败态”。“必败态”意思是这种局面摆在面前的先手必败。严格定义如下:
- 无法进行任何移动的局面是必败态;
- 可以移动到必败态的局面是非必败态;
- 在必败态下做的所有操作都是非必败态
显然在Nim博弈中,必败态为a1^a2^a3…^an=0,因为此时他不能进行任何移动。因此我们需要证明该必败态满足上述三个条件。
- 显然满足
- 对于某一个局面(a1,a2,a3…an),若a1^a2^a3…^an不为0,则一定存在某个合法的移动,将某一堆石子ai变为ai’后,使得a1^a2^a3^ai’…^an=0。不妨假设a1^a2^a3…^an=k。则一定存在某个ai,它的二进制表示的在k的最高位为1(否则,这个k最高位的1哪来的)。同时一定有ai^k<ai。我们令ai’=ai^k。则a1^a2^a3…^a^k=0。(即等式a1^a2^a3…^an=k两边同时对k取异或)
- 对于某一个局面(a1,a2,a3…an),若a1^a2^a3…^an=0。一定不存在某一个合法的移动,将某一堆石子ai变为ai’后,使得a1^a2^a3^ai’…^an=0。因为a1^a2^a3^ai’…^an=a1^a2^a3…^an,根据消去律,可得ai’=ai。
进一步扩展,我们定义SG函数。在这之前,首先定义在集合上的操作mex(minimal excludant),表示不属于该集合的最小的自然数。如mex{0,1,2}=3,mex{2,3}=0,mex{{}}=0。同时,我们将博弈的状态空间分为两类,P-position和N-position。P-position为表示先手必败的局面(状态),N-position表示后手必败的局面。因此Nim博弈可以重新定义为:
- 合法操作集合为空的局面是P-position;
- 可以移动到P-position的局面是N-position;(N-position可以通过移动变为P-position)
- 所有移动都只能到N-position的局面是P-position(无论如何移动,P-position只能向N-position转化)
对于刚刚的Nim博弈,根据定理Bouton’s Theorem:
该局面为P-position的充要条件是a1^a2^a3…^an=0。
给定一个有向无环图,定义关于图的每个顶点的SG函数定义为SG(x)=mex{SG(y)|x->y}。例如,有 1 堆 n 个的石子,每次只能取 1,3,4 个石子,先取完石子者胜利,那么各个数的 SG 值为多少?
- SG(0)=mex({})=0
- SG(1)=mex({SG(0)})=mex({0})=1
- SG(2)=mex({SG(1)})=mex({1})=0
- SG(3)=mex({SG(2),SG(0)})=mex({0})=1
- SG(4)=mex({SG(0),SG(1),SG(3)})=mex({0,1})=2
- SG(5)=mex({SG(1),SG(2),SG(4)})=mex({1,0,2})=3
SG值的意义:SG函数实际上代表的一个状态值。如果值为 0,表示当前已经是P状态,否则表示通过某一操作可以到达P状态(即当前状态为N状态)。
进一步考虑 SG函数与Nim博弈。当SG(x)=k时,表明对于任意一个0<=i<k,都存在 x的一个后继y满足 SG(y)=i也就是说,当某枚棋子的SG值是k时,我们可以把它变成0、变成1、⋯、变成k−1,但绝对不能保持k不变。
Sprague-Grundy Theorem:
SG(G)=SG(G1)^SG(G2)^SG(G3)^SG(G4)…SG(Gn)
当SG(G)为0时,先手必输。
该方法本质是还是采取的从结果倒推的方法,只不过使用了一个科学的有规律可寻的方法。
int sg[N];
//s为一次可以拿走的石头的个数,t为数组长度
void cal_SG(int* s, int t)
{
int i, j;
//hash[i]为状态i的下一个状态的SG值
bool hash[N];
memset(sg, 0, sizeof(sg));
for (i = 1; i <= N; i++) {
memset(hash, 0, sizeof(hash));
//遍历所有可以取的值(求可以到达的所有下一状态)
for (j = 0; j<t; j++) {
if (i - s[j] >= 0) {
hash[sg[i - s[j]]] = 1;
}
}
//find the mex of the set
for (j = 0; j <= N; j++) {
if (!hash[j]) { break; }
}
sg[i] = j;
}
}
参考:
- http://sighingnow.github.io/algorithm/game_theory_sg_function.html
- https://hrbust-acm-team.gitbooks.io/acm-book/content/game_theory/sghan_shu.html