SG函数


$SG$ 函数

$SG$ 函数是用于处理公平组合游戏($ICG$)。

  • $ICG$ 性质:

  • 两名玩家。

  • 轮流操作,在任意的有限集合进行操作,并改变局面。(也就是说棋子是“公共”的,不分“黑白”。)

  • 一个局面的合法操作,只取决于游戏局面本身且固定存在,与玩家顺序和任何其它因素都无关。

  • 无法操作的人输掉游戏。

$x,y$ 表示某种状态,$x\to y$ 表示 $x$ 操作一次就能到达 $y$ 的状态。$mex$ 表示一个集合中最小的自然数。例如:$mex(\{1,2,3\})=0$,$mex(\{0,1,3\})=2$。

如果 $sg(x)=n$,说明从当前状态可以转移到 $sg$ 为 $0,1,2,…,n-1$ 的状态。

$sg(x)=0$ 的状态是必败态。此时没有操作可以做,那么就算是输掉比赛。

$Nim$ 游戏

P2197 【模板】nim 游戏

如果只有一堆石头很容易得出:$sg(x)$ 表示剩余 $x$ 颗石子的状态的 $sg$ 的值。

只有一堆,每次可以拿任意个石头。显然,如果还剩 $x$ 颗石头,那么可以转换到还剩 $0,1,2,3,4,5,…,x-1$(每次至少拿一个)的状态。也就是说 $sg(x)=x$。

现在假设有两堆石头,继续玩 $Nim$ 游戏,假设 $sg(n,m)$ 表示剩余石子数分别为 $n$ 和 $m$ 的状态的 $sg$ 值。同样,不难发现,有:

其中:

同样:

但是,如果是 $3,4,5,6,…$ 堆的时候会非常的麻烦。

$SG$ 定理

设 $x,y$ 为 $ICG$ 状态(或者将 $x,y$ 可以看成子游戏),则:$sg(x+y)=sg(x)\oplus sg(u)$

$\oplus$ 就是异或。

如果有 $sg(x)\oplus sg(y)\not =0$ 那么,就一定可以转换到异或和为 $0$,将值较大的转换为和较小的值相同。同样如果 $sg(x)\oplus sg(u)=0$,那么就要想办法转移成不为 $0$ 的状态,如果转移不了了,就形成了必胜状态和必败状态。

  • $SG$ 定理推广

设 $x_1,x_2,x_3,…,x_n$ 为 $ICG$ 的状态,则:$sg(x_1+x_2+x_3+…+x_n)=sg(x_1)\oplus sg(x_2)\oplus …\oplus sg(x_n)$

这时就能处理有很多堆石头的 $Nim$ 游戏了,如果有 $n$ 堆石头(可以把每堆石头看成上面说的 $x_1,x_2,…,x_n$ 代表第 $1,2,…,n$ 堆)的异或和为 $0$ 时,游戏必败。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cctype>
#include<queue>
using namespace std;
int r_r(){//快读 
  int k=0,f=1;
  char c=getchar();
  while(!isdigit(c)){
    if(c=='-')f=-1;
    c=getchar();
  }
  while(isdigit(c)){
    k=(k<<1)+(k<<3)+(c^48);
    c=getchar(); 
  }
  return k*f;
}
int t=r_r();//多测 
int main(){
  while(t--){
    int n=r_r();//每组有多少堆 
    int s=0;
    for(int i=1;i<=n;i++)s^=r_r();//异或每堆的石子数量 
    
    //输出 
    if(!s)puts("No");
    else puts("Yes"); 
  }
}

SDOI2009 E&D

P2148 SDOI2009 E&D

这道题就需要先处理子游戏最后再得出结果。

将每一组 $S_i$ 看成一个子游戏,

结论:

  • $sg(x,y)=sg(y,x)$

  • 当 $x,y$ 都是奇数时,$sg(x,y)=0$。

  • 当 $x$ 是偶数时(注意 $x,y$ 的位置可以互换)$sg(x,y)=sg(\frac{x}{2},\lfloor{\frac{y-1}{2}}\rfloor+1)+1$。

代码

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
#include<bitset>
#include<stack>
using namespace std;
long long r_r(){//快递 
  long long k=0,f=1;
  char c=getchar();
  while(!isdigit(c)){
    if(c=='-')f=-1;
    c=getchar();
  }
  while(isdigit(c)){
    k=(k<<1)+(k<<3)+(c^48);
    c=getchar(); 
  }
  return k*f;
}
const int o_o=1e3+10;
int main(){
  int t=r_r();
  while(t--){
    int a_s=0;
    int n=r_r();
    for(int i=1;i<=n/2;i++){
      int a=r_r(),b=r_r();
      int s_i=0;
      while(a||b){//如果是 sg(0,0),直接返回 
        if(a&1&&b&1)break;//都是奇数,直接退出 
        if(a&1)swap(a,b);//保证 a 是偶数 
        a>>=1;
        b=(b-1)>>1;
        b++;
        s_i++;//累计答案 
      }
      a_s^=s_i;//累计 
    }
    puts(a_s?"YES":"NO");//判断 
  }
  return 0;
}

也可写成:

设 $x_i=(x-1)|(y-1)$,

那么 $sg(x_i)=sg(\frac{x_i}{2})+1$

代码

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
#include<bitset>
#include<stack>
using namespace std;
long long r_r(){
  long long k=0,f=1;
  char c=getchar();
  while(!isdigit(c)){
    if(c=='-')f=-1;
    c=getchar();
  }
  while(isdigit(c)){
    k=(k<<1)+(k<<3)+(c^48);
    c=getchar(); 
  }
  return k*f;
}
const int o_o=1e3+10;
int main(){
  int t=r_r();
  while(t--){
    int a_s=0;
    int n=r_r()>>1;
    while(n--){
      int x_p=0;
      int x=(r_r()-1)|(r_r()-1);//计算 x_i 
      while(x&1){//统计贡献 
        ++x_p;
        x>>=1;
      }
      a_s^=x_p;//累计子任务的贡献 
    }
    puts(a_s?"YES":"NO");//输出结果 
  }
  return 0;
}

对于可以用 $SG$ 函数的题而言,关键在于找到 $SG$ 函数。如果实在推不出来,就用暴力枚举,打表找规律。


文章作者: 王大神——A001
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 王大神——A001 !
  目录