简单博弈之SG函数

SG函数 , 需要尼姆博奕(Nimm Game) 作为基础

SG函数其实非常的简单 , 我初学的时候也被网上的花哨的bolg给吓到了

单个游戏的SG


知识点1 :

学完之后的SG -> 判断一个状态的SG值为非0值 , 便为一个必胜态 ; 只要SG=0才是必败态


知识点2 :

对集合函数mex -> 返回未在集合中的最小非负整数 , 例如mex{1,2}=0 , mex{0,2}=1 , mex{}=0


知识点3 :

S的后继状态代表对状态S进行一次操作后产生的状态

状态x的SG函数值 -> SG(x)=mex{S|S为x后继状态}


归纳 :

预处理一个状态x的SG值 , 只要处理出其后继状态的SG值 , 然后找到这些SG值中没有出现过的非负整数 , 就是x的SG值


代码实现 :

–> 例题
题意 : 多个堆 , 有一个m个元素的集合 , 每次操作可以从堆中remove的数量只能是集合中的数


首先是暴力打表 , 从小的状态处理到大的状态 , 枚举所有操作得出的下一状态(从小到大所以保证了下一状态已经处理出了) , 然后求这些状态的mex便是当前状态的SG

首选暴力打表,因为时间是一样的


int sg[10009];
bool vis[10009];

void init(int*s,int m){
        //本题有m个操作,分别为拿走s[i]个珠子
    memset(sg,0,sizeof(sg));
    for(int i=1;i<=10000;i++){//10000为珠子数量上限
        memset(vis,0,sizeof(vis));
            //因为memset vis数组的次数太多了, 所以只能用bool, 用int会TLE
        for(int j=1;j<=m&&s[j]<=i;j++){//遍历所有操作
            vis[sg[i-s[j]]]=1;//维护mex,不是vis状态而是状态的SG值
        }
        for(int j=0;j<=10000;j++){//找最小
            if(!vis[j]){sg[i]=j;break;}
        }
    }
}

这里给大家分享几种dfs的写法 , 如果不知道sg的范围只能开状态数的范围了,会MLE

但是我后来测试了一下发现SG值不会超过40 , 所以vis只需要开40就可以过了

//如果是dfs的话,下面的vis数组不能和上面的用一个,不然会错
//所以有两种做法
//做法一:二维vis
bool vis[状态数][sg数];//vis[10001][40]
int dfs(int i,int *s,int m){
    if(sg[i]!=-1)return sg[i];
    for(int j=1;j<=m&&s[j]<=i;j++){
        vis[i][dfs(i-s[j],s,m)]=1;
    }
    for(int j=0;;j++){
        if(!vis[i][j])return sg[i]=j;
    }
}

int main(){ 
    memset(sg,-1,sizeof(sg));
    for(int i=1;i<=n;i++)scanf("%d",&a),Nim=dfs(a,s,m);
        //不用预处理直接dfs,遇到没有处理过的状态才处理
}



//做法二:dfs时开vis数组
int dfs(int i,int *s,int m){
    if(sg[i]!=-1)return sg[i];
    int *vis=new int[40];
    for(int j=0;j<40;j++)vis[j]=0;//函数内不能用sizeof(指针)

    for(int j=1;j<=m&&s[j]<=i;j++){
        vis[dfs(i-s[j],s,m)]=1;
    }
    for(int j=0;;j++){
        if(!vis[j]){sg[i]=j;break;}
    }
    delete vis;
    return sg[i];

}

int main(){ 
    memset(sg,-1,sizeof(sg));
    for(int i=1;i<=n;i++)scanf("%d",&a),Nim=dfs(a,s,m);
        //不用预处理直接dfs,遇到没有处理过的状态才处理
}

多个游戏的SG

什么是多个游戏? 比如有一堆石子给双方取就是一个游戏, 多个堆给双方取就是多个游戏, 你有没有觉得和Nim博弈的描述很像?

对于多个游戏 , 处理出每个游戏现在状态的SG值 , 然后和Nim博弈那样每个SG值异或起来就是整个的SG值 . 当然和Nim博弈一样 , 异或后的结果如果是0那么就是必败态 , 否则为必胜态


例题代码

//打表版
#include<bits/stdc++.h>
using namespace std;
int read(){ int ans=0; char last=' ',ch=getchar();
while(ch<'0' || ch>'9')last=ch,ch=getchar();
while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
if(last=='-')ans=-ans; return ans;
}


int sg[10009];
bool vis[40];

void init(int*s,int m){
    memset(sg,0,sizeof(sg));
    for(int i=1;i<=10000;i++){
        memset(vis,0,sizeof(vis));
        for(int j=1;j<=m&&s[j]<=i;j++){
            vis[sg[i-s[j]]]=1;
        }
        for(int j=0;;j++){
            if(!vis[j]){sg[i]=j;break;}
        }
    }
}

int main(){
    int m;
    int s[109];
    while(scanf("%d",&m)==1){if(!m)break;
        for(int i=1;i<=m;i++)scanf("%d",&s[i]);
        sort(s+1,s+1+m);
        init(s,m);

        int t=read();
        while(t--){
            int Nim=0,a;
            int n=read();
            for(int i=1;i<=n;i++)scanf("%d",&a),Nim^=sg[a];
            if(Nim==0)printf("L");
            else printf("W");
        }
        printf("\n");
    }
}
//dfs版
#include<bits/stdc++.h>
using namespace std;
int read(){ int ans=0; char last=' ',ch=getchar();
while(ch<'0' || ch>'9')last=ch,ch=getchar();
while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
if(last=='-')ans=-ans; return ans;
}

int sg[10009];

int dfs(int i,int *s,int m){
    if(sg[i]!=-1)return sg[i];
    int *vis=new int[40];
    for(int j=0;j<40;j++)vis[j]=0;

    for(int j=1;j<=m&&s[j]<=i;j++){
        vis[dfs(i-s[j],s,m)]=1;
    }
    for(int j=0;;j++){
        if(!vis[j]){sg[i]=j;break;}
    }
    delete vis;
    return sg[i];

}

int main(){
    int m;
    int s[109];
    while(scanf("%d",&m)==1){if(!m)break;
        for(int i=1;i<=m;i++)scanf("%d",&s[i]);
        sort(s+1,s+1+m);
        memset(sg,-1,sizeof(sg));

        int t=read();
        while(t--){
            int Nim=0,a;
            int n=read();
            for(int i=1;i<=n;i++)scanf("%d",&a),Nim^=dfs(a,s,m);
            if(Nim==0)printf("L");
            else printf("W");
        }
        printf("\n");
    }
}

猜你喜欢

转载自blog.csdn.net/jk_chen_acmer/article/details/82144464