1. 程式人生 > >SG函式入門

SG函式入門

參考部落格:https://baike.baidu.com/item/SG%E5%87%BD%E6%95%B0/1004609

https://www.cnblogs.com/ECJTUACM-873284962/p/6921829.html

主要參考百度百科:

首先定義mex(minimal excludant)運算,這是施加於一個集合的運算,表示最小的不屬於這個集合非負整數。例如mex{0,1,2,4}=3、mex{1,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函式的性質:

首先,所有的terminal position(目標位置)所對應的頂點,也就是沒有出邊的頂點,其SG值為0,因為它的後繼集合是空集。然後對於一個g(x)=0的頂點x,它的所有前驅y都滿足 g(y)!=0。對於一個g(x)!=0的頂點,必定存在一個後繼y滿足g(y)=0

表明,頂點x所代表的postion(位置)是P-position(必敗點)當且僅當g(x)=0

 

SG值的意義

1,當g(x)=k時,表明對於任意一個0<=i<k,都存在x的一個後繼y滿足g(y)=i。為什麼一定存在呢?

 因為mex函式運算時,g(y)等於最小的不屬於

(y後繼狀態)這個集合的非負整數,換言之,y的後繼狀態一定有 [0,k-1] 這個範圍才會出現g(x)=k,k為不小於後繼狀態(集合),因為函式g就是按照mex運算得來的。

2,當某枚棋子的SG值是k時,我們可以把它變成0、變成1、……、變成k-1,但絕對不能保持k不變。這個聯想到Nim遊戲, Nim遊戲的規則就是:每次選擇一堆數量為k的石子,可以把它變成0、變成1、……、變成k-1,但絕對不能保持k不變。這表明,如果將n枚棋子所在的頂 點的SG值看作n堆相應數量的石子,那麼這個Nim遊戲的每個必勝策略都對應於原來這n枚棋子的必勝策略!

3,對於n個棋子,設它們對應的頂點的SG值分別為(a1,a2,…,an),再設局面(a1,a2,…,an)時的Nim遊戲的一種必勝策略是把ai 變成k,那麼原遊戲的一種必勝策略

就是把第i枚棋子移動到一個SG值為k的頂點。這就相當於可以看做,每次最聰明的操作是按照Nim遊戲SG(N)值來移動,例如我們想把SG(N)變為SG(小於N),就滿足最聰明移動,但我們真正移動的又不是SG(N)的值,移動的是原來頂點N的值,咦,好像也是哦,沒關係,我們依舊移頂點N的值,只是說移動頂點N的值時,要滿足移動後頂點(小於N),SG(小於N)的值是我們想要的最聰明的SG值。        

             

這就好像我們敲程式碼,我們通過高階程式語言來間接寫機器語言執行計算機。

 

計算1~n的SG函式值步驟如下:

1、使用 陣列f可改變當前狀態 的方式記錄下來。

2、然後我們使用 另一個數組 將當前狀態x 的後繼狀態標記。

3、最後模擬mex運算,也就是我們在標記值中 搜尋 未被標記值 的最小值,將其賦值給SG(x)。

4、我們不斷的重複 2 - 3 的步驟,就完成了 計算1~n 的函式值

程式碼:

//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;
        }
    }
}

 

給出一道入門級的題目

hdu 1848

#include <cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=1010;
#define N 20
int f[N],SG[MAXN],S[MAXN];///f[]可走步數//S[]後繼存在狀態
void getSG(int n){
    int i,j;
    memset(SG,0,sizeof(SG));///PN點
    for(i = 1; i <= n; i++){///從i=1開始
        memset(S,0,sizeof(S));///每次初始化S[]後繼存在狀態
        for(j = 0; f[j] <= i && j < N; j++)///f[i]<i;
            S[SG[i-f[j]]] = 1;///f[]可走步數///S[]後繼存在狀態
        for(j = 0;;j++) if(!S[j]){///找出後繼存在狀態中的最小非負整數;
            SG[i] = j;?///最終得出該點i的SG值
            break;}
    }
}
int main(){
    int n,m,k;
    f[0] = f[1] = 1;
    for(int i = 2; i <= 16; i++)///由題意得初始化可走步數(可取個數)
        f[i] = f[i-1] + f[i-2];
    getSG(1000);///得出所有點的SG函式值
    while(scanf("%d%d%d",&m,&n,&k),m||n||k){
        if(SG[n]^SG[m]^SG[k]) printf("Fibo\n");
        else printf("Nacci\n");
    }
    return 0;
}

 

 

我的標籤:做個有情懷的程式設計師。