博弈——sg函数实现和讲解

来源:互联网 发布:新版php卡盟排行榜源码 编辑:程序博客网 时间:2024/06/18 08:12

转载自:http://blog.csdn.net/qq_30241305/article/details/50819784


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)

[cpp] view plain copy
  1. <pre name="code" class="cpp">const int MAXN=1005;  
  2.   
  3. int a[MAXN],sg[MAXN];  
  4.   
  5.   
  6. void init()  
  7. {  
  8.     a[1] = 1,a[2] = 2;  
  9.     for(int i = 3; i < 20; i ++)  
  10.     {  
  11.         a[i] = a[i - 1] + a[i - 2];  
  12.         // cout<<a[i]<<endl;  
  13.     }  
  14.  }  
  15.   
  16. int SG(int x)  
  17. {  
  18.     bool vis[105] = {false};  
  19.     int temp;  
  20.     for(int i = 0; i <n && a[i]<= x; i ++)//n是i的个数  
  21.     {  
  22.         temp= x - a[i];  
  23.         if(sg[temp]== -1)  
  24.         {  
  25.             sg[temp] = SG(temp);  
  26.         }  
  27.         vis[sg[temp]]= true;  
  28.     }  
  29.     for(int i = 0;; i ++)  
  30.     {  
  31.         if(vis[i]== false)  
  32.         {  
  33.             return i;  
  34.         }  
  35.     }  
  36. }  


2.      打表法

调用方法:sg[n]

[cpp] view plain copy
  1. const int MAXN=1005;  
  2.   
  3. int a[MAXN],sg[MAXN],b[MAXN];  
  4. int n,maxx;//maxx表示sg[]表的大小。n表示的是a[]的大小,也就是每一步所能走的值的集合的大小  
  5. void init()  
  6. {  
  7.     a[1] = 1,a[2] = 2;  
  8.     for(int i = 3; i < 20; i ++)  
  9.     {  
  10.         a[i] = a[i - 1] + a[i - 2];  
  11.         // cout<<a[i]<<endl;  
  12.     }  
  13.  }  
  14.   
  15. void SG()  
  16. {  
  17.     for(int i = 0; i <= maxx; i ++)  
  18.     {  
  19.         memset(b,true,sizeof(b));  
  20.         for(int j = 0; j < n; j ++)  
  21.         {  
  22.             if(i < a[j])  
  23.                 break;  
  24.             b[sg[i - a[j]]] = false;//不是i - a[j]是sg[i-a[j]]  
  25.   
  26.         }  
  27.         for(int j = 0; j <= maxx; j ++)  
  28.   
  29.         {  
  30.             if(b[j])  
  31.             {  
  32.                 sg[i] = j;  
  33.                 break;  
  34.             }  
  35.         }  
  36.     }  
  37. }  

原创粉丝点击