\(SG\) 函数

本文借鉴了自为风月马前卒的博客

基本定理:

\(ICG\) 游戏:

  1. 游戏两人轮流,并且决策最佳
  2. 无法决策时游戏结束,并且在有限步内结束。
  3. 同一个状态不能多次表达,且没有平局出现。
  4. 游戏者在任意状态做出的决策和自己无关,只与当前状态有关。

满足以上条件就为 \(ICG\) 游戏,属于组合游戏。

\(nim\) 游戏就是一种 \(ICG\) 游戏。

必胜和必败:

  1. 无法移动的状态为必败 \(P\)
  2. 可以移动到必败( \(P\) )的局面为必胜( \(N\) )
  3. 所有移动都会进入 \(N\) 的局面为 \(P\).

\(DAG\) 中的博弈

定义:给定一张有向无环图,起始点有一个棋子,轮流挪动,不能移动的人算输。

事实上,所有的 \(ICG\) 游戏都可以抽象成为这种游戏。

即把一个状态向另一个状态连边,判断状态合法性

正式介绍:

对于给定的有向无环图,定义每个点的 \(SG\) 函数为:

\[SG(x)=mex\{SG(y)|x能到达y\} \]

\(mex\) 就是最小的不属集合的非负整数。

性质:

  1. 所有汇点的 \(SG\) 函数为 \(0\),因为所有汇点后继状态都是空集。
  2. \(SG(x)=0\) 该节点为必败点。
    此性质决定了该节点后继节点 \(SG\) 值不为 \(0\),满足必败点定义
  3. \(SG(x)!=0\) 该节点为必胜点。
    此性质决定了该节点的后继节点中一定有一个节点 \(SG(y)=0\) ,满足必胜点定义。

这个问题实际上就是巴什博弈。

推导:

如果这个棋盘上有 \(n\) 个棋子的时候呢?

我们当 \(SG(x)=k\) 表明后继状态有 \(SG(y)=[1,k-1]\)

也就是说,我们从 \(k\) 可以转移到 \([1,k-1]\) 的任何一个状态,当前共有 \(n\) 个棋子。

是不是像 \(nim\) 游戏?

因此,在 \(nim\) 游戏中,\(SG\) 函数异或值不为零,就先手必胜。

推广:

我们设想:游戏的和的 \(SG\) 值是他们 \(SG\) 值的异或和。

我们可以将所有的 \(ICG\) 问题对应到 \(DAG\) 上,然后通过 \(SG\) 函数之间的转移解决所有问题。

所以说当我们面对由 \(n\) 个游戏组合成的一个游戏时,只需对于每个游戏找出求它的每个局面的SG值的方法,就可以把这些 \(SG\) 值全部看成 \(Nim\) 的石子堆,

然后依照找 \(Nim\) 的必胜策略的方法来找这个游戏的必胜策略了!( \(Nim\) 其实就是 \(n\) 个从一堆中拿石子的游戏求 \(SG\) 的变型,总 \(SG = n\)\(sg\) 的异或.

解题模型:

把原游戏分解成独立的子游戏,原游戏的 \(SG\) 函数值是它所有子游戏的 \(SG\) 函数值异或和。

考虑分别设计每一个子游戏:

计算方法:

  1. 可选步数为 \([1,m]\) 的连续整数,直接取模即可 \(SG(x)=x\%(m+1)\)
  2. 可选择步数为任意步 \(SG(x)=x\)
  3. 可选步数为一系列不连续的数,用模板计算。

模板

  1. 打表
//f[]:可以取走的石子个数
//sg[]:0~n的SG函数值
//hash[]:mex{}
int f[N],sg[N],hash[N];     
void getSG(int n){
    memset(sg,0,sizeof(sg));
    for(int i=1;i<=n;i++){
        memset(hash,0,sizeof(hash));
        for(int j=1;f[j]<=i;j++)
            hash[sg[i-f[j]]]=1;
        for(int j=0;j<=n;j++)    //求mes{}中未出现的最小的非负整数
            if(hash[j]==0){ sg[i]=j; break;}
        
    }
}
  1. \(DFS\)
//注意 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(int i=0;i<n;i++)
        if(x>=s[i]){
            SG_dfs(x-s[i]);
            vis[sg[x-s[i]]]=1;
        }
    int e;
    for(int i=0;;i++)
        if(!vis[i]){ e=i; break; }
    return sg[x]=e;
}
  1. 计算:

\[SG(x)=SG(x_1)^SG(x_2)^....^SG(x_n) \]

如果 \(SG(x)=0\) ,就先手必败。

例题:

P2148 [SDOI2009]E&D

每组石子是一个相对独立的游戏,对于每组两堆石子的个数暴力计算SG函数并打表.

发现后继状态 \(SG\) 值集合 \(S\) 用二进制表示的结果为 \((x_1−1)|(x_2−1)\)

因此,通过 \(SG\) 函数的性质,即可算出这道题。

#include<bits/stdc++.h>
using namespace std;
int T,n;
int main(){
    cin>>T;
    while(T--){
        cin>>n; n>>=1;
        int sg=0;
        for(int i=0,x,y;i<n;i++){
            scanf("%d%d",&x,&y);
            int s=(x-1)|(y-1),mex=0;
            for(;s&1;s>>=1) mex++;
            sg^=mex;
        }
        puts(sg?"YES":"NO");
    }
    system("pause");
    return 0;
}