博弈sg函数

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)

<pre name="code" class="cpp">const int MAXN=1005;

int a[MAXN],sg[MAXN];


void init()
{
    a[1] = 1,a[2] = 2;
    for(int i = 3; i < 20; i ++)
    {
        a[i] = a[i - 1] + a[i - 2];
        // cout<<a[i]<<endl;
    }
 }

int SG(int x)
{
    bool vis[105] = {false};
    int temp;
    for(int i = 0; i <n && a[i]<= x; i ++)//n是i的个数
    {
        temp= x - a[i];
        if(sg[temp]== -1)
        {
            sg[temp] = SG(temp);
        }
        vis[sg[temp]]= true;
    }
    for(int i = 0;; i ++)
    {
        if(vis[i]== false)
        {
            return i;
        }
    }
}


 
 
 

2.      打表法

调用方法:sg[n]

const int MAXN=1005;

int a[MAXN],sg[MAXN],b[MAXN];
int n,maxx;//maxx表示sg[]表的大小。n表示的是a[]的大小,也就是每一步所能走的值的集合的大小
void init()
{
    a[1] = 1,a[2] = 2;
    for(int i = 3; i < 20; i ++)
    {
        a[i] = a[i - 1] + a[i - 2];
        // cout<<a[i]<<endl;
    }
 }

void SG()
{
    for(int i = 0; i <= maxx; i ++)
    {
        memset(b,true,sizeof(b));
        for(int j = 0; j < n; j ++)
        {
            if(i < a[j])
                break;
            b[sg[i - a[j]]] = false;//不是i - a[j]是sg[i-a[j]]

        }
        for(int j = 0; j <= maxx; j ++)

        {
            if(b[j])
            {
                sg[i] = j;
                break;
            }
        }
    }
}


 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值