博弈小节 NIM SG BASH GAME Wythoff Game

来源:互联网 发布:java编写cad绘图工具 编辑:程序博客网 时间:2024/06/06 18:08

这两天一直在做博弈专题,开始做确实是一脸懵逼,做了两天还是有点收获,顺便做个基础性的总结。

基础博弈题:1.Bash Game —— 巴什博弈;

                      2.威佐夫博弈—— Wythoff Game;

                      3.NIM Game;

                      4.SG函数。

TMD 敲了一上午没保存,又得重来。


1.Bash Game:    从一堆物品里面依次取出物品,每次不超过 n 个。


        假设总共有 m 个物品,首先我们考虑最后一步,假设先手赢,则最后一步 m' <=n ,那么倒数第二步对于后手应该有 m' >=(n+1) , 现在我们从整体上来分析:

        1.  如果一开始有  m %(n+1)==0,  此时对于先手不管他取了多少个,后手只需要让  接下来的  物品总数满足  m' %(n+1)==0  就可以保证赢,此时就是先手必败局势。 

         2. m%(n+1)!=0, 这个时候我们考虑让后手变成  1  中的先手,即先让  先手取出  m%(n+1)个物品,此时后手面对的局势就是 1 中先手的局势了,即不管后手取多少个,先手只需要让总数满足 m'%(n+1) ==0 就能保证必赢。


         http://acm.hdu.edu.cn/showproblem.php?pid=1846    裸的Bash Game.


2. Wythoff Game: 两堆物品,两个人依次取出物品,两种方法:从一堆里面取出任意数量的物品 or 从两堆中取出相同数量的物品。


      这个问题需要好好分析一下:

      首先定义几个概念:

1.  局势: 某个时刻两堆物品的数量。

2.  奇异局势:  比如路人甲面对的局势为(0,0) ,此时必败,就称为  奇异局势  。前几个奇异局势有(0,0),

(1,2),(3,5),(4,7),(6,10),(8,13),(9,15)


可以看出奇异局势所具有的性质:

     1.任何自然数都包含在一个且仅有一个奇异局势中;

     2.任意操作都可将奇异局势变为非奇异局势。

     3.采用适当的方法,可以将非奇异局势变为奇异局势。

证明:  (a , b ),  a(k)为前面未出现的最小自然数,b(k)=k+a(k);


对  1: a(k)>a(k-1), 即有:  b(k)=k+a(k)>a(k-1)+k-1=b(k-1)>a(k-1);


对  2: 由于奇异局势已经固定,当某个值改变 的时候不可能变为  其他奇异局势。


对  3 : 显然成立嘛,这个就不证明了。



一大堆废话:  其实就是对于非奇异局势,先手必胜,否者后手必胜。

那么怎么来判断给定的(a,b )  是否为奇异局势呢:


int  K=abs(a-b);   if(  K*(1+sqrt(5)/2 ==  min(a,b)    )   即证明为奇异局势。


      http://acm.hdu.edu.cn/showproblem.php?pid=1527   裸的Wythoff Game

      http://acm.hdu.edu.cn/showproblem.php?pid=2177   

    (   首先讨论在两边同时取的情况,很明显两边同时取的话,不论怎样取他的差值是不会变的,那么我们可以根据差值计算出其中的小的值,然后加上差值就是大的一个值,当然能取的条件是求出的最小的值不能大于其中小的一堆的石子数目。加入在一堆中取的话,可以取任意一堆,那么其差值也是不定的,但是我们可以枚举差值,差值范围是0 --- 大的石子数目,然后根据上面的理论判断满足条件的话就是一种合理的取法。)  COPY BY  

http://blog.csdn.net/y990041769/article/details/21694007


3.NIM Game:  三堆物品,依次取,每次只能取一堆里面的物品,最后一个取完的胜利。


NIM Game 较于前面的两种还是难一点的吧。

        首先,我们可以看出这种类型的几种奇异局势:(0,0,0),(0,n,n),(1,2,3)

        然后就是奇怪的  异或  运算了,按位模2加,相当于二进制加法但是  1^1==0。

        定义  异或符号 :“ ^ ”;

        对于这几种奇异局势有:

        即有:

               (0,0,0)^=0;(0,n,n)^=0;(1,2,3)^=0   

              我们可以推广到任何奇异局势都有  (a,b,c)^=0    

              那么对于其他的非奇异局势有:假设(a,b,c)且(a<b<c)  ,我们只需要把c=a^b  ;

              这时候就有   a^b^c=c^c=0

              c=c-a^b;

              具体分析:

             对于某个局面(a1,a2,...,an),若a1^a2^...^an!=0,一定存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。不妨设a1^a2^...^an=k,则一定存在某个ai,它的二进制表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时 ai^k<ai一定成立。则我们可以将ai改变成ai'=ai^k,此时a1^a2^...^ai'^...^an=a1^a2^...^an^k=0。   

                                                                                                                          注意记住红字的内容!!!

 

    也是一大段废话,只是为后面的做一下铺垫。


那么对于n堆物品我们应该怎么看呢: 首先根据奇异局势是不够用的。

参考网上大致有两种方法,但思路应该是差不多的,一种是  定义  P_position 与    N_position ;还有一种是定义利己态和利他态,我们只需要这些都是让你分析问题的工具就行。下面介绍其中的一种。

        定义:P_position   :后手可保证必胜”或者“先手必败 ;直观的说上一次操作的人有必胜策略的局面是P_Positi

                   N_position   :先手必胜。

       同样有如下性质:

                  无法进行任何移动的局面(也就是terminal position)是P-position;

                   可以移动到P-position的局面是N-position

                  所有移动都导致N-position的局面是P-position;

       先给出证明吧:

                   对于P_position  只有一种情况,即全为零,性质1证得;

                   对于2,如果存在一种局面(a1,a2,a3,a3......an),有a1^a2^......^an!=0;一定存在一种方法让局势变为0,即a1^a2^a3^......^an==0   ;假设原式等于k,k的二进制最高位明显为1,这是一定有a(i)^k<a(i);其实就是原式的结果为  k,  现在要让他变为 0,由于k^k  = 0,只需要再个原式 ^k  就行。

                  对于3,我们可以证明的是任何一个P_position 合法移动后不可能再出现一个P_position。 

     (对于任意一个局面,只要找到一个是P-position的子局面就能说明是N-position)  由定理二。

下面利用工具进行计算:

              以(3,3) 为例子,(3,3)的后继只能是(1,3),(2,3),(0,3),(0,3)的后继有(0,0)——P_position,即(0,3)为N_position,对于(1,3)的后继有(1,1)为P_position ,由于  (1,1)的后继只有(0,1)为N_position ,(1,3)为N_position;同理大法来了,(2,3)也为N_position。即对于(3,3)后继都为N_position,(3,3)为P_position。即对于任何一个局面我们需要递归找到子局面的状态,然后在得出当前状态。

 

前面的废话终于说完了,看不懂的还是慢慢摸索吧,也可以暂时跳过咯,记一个结论吧:


     对于一个Nim游戏的局面(a1,a2,...,an),它是P-position当且仅当a1^a2^...^an=0,其中^表示异或(xor)运算。




4.SG函数:

        定义:   P    必败点     N    必胜点

        性质:   1.  所有终结点是 必败点 P 。(我们以此为基本前提进行推理,换句话说,我们以此为假设)

                       2.   从任何必胜点N 操作,至少有一种方式可以进入必败点 P。

                      3.   无论如何操作,必败点P 都只能进入 必胜点 N。  (有没有很熟悉)

通常我们分析必胜点和必败点都是以终结点进行逆序分析。


Sprague_Grundy定理:  游戏和的SG函数等于各个游戏SG函数的Nim和。


       首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。

       对于任意状态 x , 定义 SG(x) = mex(S),其中 S 是 x 后继状态的SG函数值的集合。如 x 有三个后继状态分别为 SG(a),SG(b),SG(c),那么SG(x) = mex{SG(a),SG(b),SG(c)}。 这样 集合S 的终态必然是空集,所以SG函数的终态为 SG(x) = 0,当且仅当 x 为必败点P时。


【实例】取石子问题

有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;

以此类推.....

   x        0  1  2  3  4  5  6  7  8....

SG[x]    0  1  0  1  2  3  2  0  1....

由上述实例我们就可以得到SG函数值求解步骤,那么计算1~n的SG函数值步骤如下:

1、使用 数组f 将 可改变当前状态 的方式记录下来。

2、然后我们使用 另一个数组 将当前状态x 的后继状态标记。

3、最后模拟mex运算,也就是我们在标记值中 搜索 未被标记值 的最小值,将其赋值给SG(x)。

4、我们不断的重复 2 - 3 的步骤,就完成了 计算1~n 的函数值。


                                                                                                                                               原文章 :      点击


对于SG函数有两个模板下面附上:





                                             贴一个GitHub  上的  文章             点击打开链接





//f[]:可以取走的石子个数//sg[]:0~n的SG函数值//hash[]:mex{}int f[N],sg[N],hash[N];     void getSG(int n){    int i,j;    memset(sg,0,sizeof(sg));    for(i=1;i<=n;i++)    {        memset(hash,0,sizeof(hash));        for(j=1;f[j]<=i;j++)            hash[sg[i-f[j]]]=1;        for(j=0;j<=n;j++)    //求mes{}中未出现的最小的非负整数        {            if(hash[j]==0)            {                sg[i]=j;                break;            }        }    }}//注意 S数组要按从小到大排序 SG函数要初始化为-1 对于每个集合只需初始化1遍//n是集合s的大小 S[i]是定义的特殊取法规则的数组int s[110],sg[10010],n;int SG_dfs(int x){    int i;    if(sg[x]!=-1)        return sg[x];    bool vis[110];    memset(vis,0,sizeof(vis));    for(i=0;i<n;i++)    {        if(x>=s[i])        {            SG_dfs(x-s[i]);            vis[sg[x-s[i]]]=1;        }    }    int e;    for(i=0;;i++)        if(!vis[i])        {            e=i;            break;        }    return sg[x]=e;}