1. 程式人生 > >SG函式的一些理解

SG函式的一些理解

前置技能:nim遊戲。

任何一個博弈遊戲我們都可以把它抽象為在一個有向無環圖上的博弈。每個節點像其能轉移的節點連一條有向邊。

現在我們現在開始玩一個遊戲來理解SG函式,n個石子每個人可以取走{1,3,4}個,不能取的人敗。

那麼我們把他抽象為一個圖。每個節點x向(x-1)(x-3)(x-4)連一條有向邊。這就變成一個有向無環圖了。我們的遊戲就變為。兩個人輪流移動一個棋子,棋子的初始位置在n點,不能移動的人敗。

這個比較好理解。然後我們定義mex(minimal excludant)運算。對於任意狀態 x , 定義 SG(x) = mex(S),其中 S 是 x 後繼狀態的SG函式值的集合。如 x 有三個後繼狀態分別為 SG(a),SG(b),SG(c),那麼SG(x) = mex{SG(a),SG(b),SG(c)}。 定義SG[0]等於1即必敗點。圖如下

那麼這題必勝點和必敗點就可以很容易求出來了。

接下來我們把他擴充套件到多堆石子的情況。

在nim遊戲中每堆石子的SG值就是這堆石子的數量。他的必敗點和必勝點的確定就是n堆石子數量的異或和,本質就是SG函式的異或和。那麼我們對於多堆石子的每次只能取{1,3,4}也是和nim一樣處理。對每堆石子SG值異或得到答案即可。

那麼貼上我在網上找的程式碼。

迴圈版:

int f[N],SG[N];
bool S[M];
void getSG(int n)
{
    memset(SG,0,sizeof(SG));
    for(int i=1;i<=n;i++)
    {
        memset(S,false,sizeof(S));
        for(int j=1;f[j]<=i&&j<M;j++)
        {
             S[SG[i-f[j]]]=true;
        }
        for(int j=0;;j++)
        if(!S[j])
        {
            SG[i]=j;
            break;
        }
    }
}

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(i=0;i<n;i++)
    {
        if(x>=s[i])
        {
            SG_dfs(x-s[i]);
            vis[sg[x-s[i]]]=1;
        }
    }
    int e;
    for(i=0;;i++)
        if(!vis[i])
        {
            e=i;
            break;
        }
    return sg[x]=e;
}