SG函数应用加简单题目

首先推荐几个不错的博客,1点击打开链接这个博客讲的很全,但是后面有点抽象,2点击打开链接这个博客在看懂前一个博客基础上来看很舒服,语言很通俗;

在搞懂了NP状态下,一切就可以好好解释了。

N状态是当前必胜态,P是当前必输态,当我们判断当前状态是N还是P的时候我们可以这样判断。

通过题目给你的要求我们可以将当前未知情况转移的已知问题结果上面:

1.如果可以转移的所有子问题状态都是N 那我们可以得出结论,当前为P; 

2. 如果可以转移的子问题中出现一次P,那我们可以得出结论,当前为N;

(结合例子可以很清楚的想明白,例子不举了,在原来两个博客里面都有例子)



关于SG,SG就是运用了上面所说的NP与其转移状态之间的关系,进而推出来的一种结论;

复制那个博客里面:

首先定义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时。

想要看明白,举一个例子:

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;

这样我们就可以推出SG(x) 的值了,模板如下:

//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合
int f[N],SG[MAXN],S[MAXN];
void  getSG(int n){
    int i,j;
    memset(SG,0,sizeof(SG));
    //因为SG[0]始终等于0,所以i从1开始
    for(i = 1; i <= n; i++){
        //每一次都要将上一状态 的 后继集合 重置
        memset(S,0,sizeof(S));
        for(j = 0; f[j] <= i && j <= N; j++)
            S[SG[i-f[j]]] = 1;  //将后继状态的SG函数值进行标记
        for(j = 0;; j++) if(!S[j]){   //查询当前后继状态SG值中最小的非零值
            SG[i] = j;
            break;
        }
    }
}

然后对于问题最后的结果:

计算sg(G)=sg(G1)^sg(G2)^...^sg(Gn),
    sg(G)=0,即P-Position,即先手比败。



组队赛时候一道简单模板题:

Gym - 101128G

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define maxn 1111
int p,k,n,a[maxn],sg[maxn],flag[maxn];
int SG()
{
    a[0]=0;
    for(int i=1;i<=n;i++)
    {
        memset(flag,0,sizeof(flag));
        for(int j=0;j<=k;j++)
        {
            if(i>j)//题目要求至少剩一张牌
            {
                if(i-j-a[i-j]>=0)
                {
                    flag[sg[i-j-a[i-j]]]=1;
                }
            }
        }
        for(int j=0;;j++)
        {
            if(!flag[j])
            {
                sg[i]=j;
                break;
            }
        }
    }
    return sg[n];
}
int main()
{
    while(~scanf("%d%d",&p,&k))
    {
        int ans=0;
        while(p--)
        {
            scanf("%d",&n);
            for(int i=1;i<=n;i++)
            {
                scanf("%d",&a[i]);
            }
            ans^=SG();
        }
        if(ans)printf("Alice can win.\n");
        else printf("Bob will win.\n");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/sinat_36215255/article/details/80099425