Nim博弈

题目:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时,所有的石子堆都已经被拿空了,则判负。

首先介绍xor运算。xor满足交换律,结合律,消去律,并且与自己本身互为逆运算。根据这个性质可以将swap函数写为

void swap(int &a, int &b) { a=a^b; b=a^b; a=a^b; }

类似Nim博弈的问题,最重要的是要寻求“必败态”。“必败态”意思是这种局面摆在面前的先手必败。严格定义如下:

  1. 无法进行任何移动的局面是必败态;
  2. 可以移动到必败态的局面是非必败态;
  3. 在必败态下做的所有操作都是非必败态

显然在Nim博弈中,必败态为a1^a2^a3…^an=0,因为此时他不能进行任何移动。因此我们需要证明该必败态满足上述三个条件。

  1. 显然满足
  2. 对于某一个局面(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取异或)
  3. 对于某一个局面(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博弈可以重新定义为:

  1. 合法操作集合为空的局面是P-position;
  2. 可以移动到P-position的局面是N-position;(N-position可以通过移动变为P-position)
  3. 所有移动都只能到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 值为多少?

  1. SG(0)=mex({})=0
  2. SG(1)=mex({SG(0)})=mex({0})=1
  3. SG(2)=mex({SG(1)})=mex({1})=0
  4. SG(3)=mex({SG(2),SG(0)})=mex({0})=1
  5. SG(4)=mex({SG(0),SG(1),SG(3)})=mex({0,1})=2
  6. 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;
    }
}

 

参考:

 

posted @ 2017-12-31 22:57  Jelly08  阅读(291)  评论(0)    收藏  举报