Sprague-Grundy函數簡介(SG函數)

原文章:https://blog.csdn.net/strangedbly/article/details/51137432

現在我們來研究一個看上去似乎更爲一般的遊戲:給定一個有向無環圖和一個起始頂點上的一枚棋子,兩名選手交替的將這枚棋子沿有向邊進行移動,無法移動者判負。事實上,這個遊戲可以認爲是所有Impartial Combinatorial Games的抽象模型。也就是說,任何一個ICG都可以通過把每個局面看成一個頂點,對每個局面和它的子局面連一條有向邊來抽象成這個“有向圖遊戲”。
下面我們就在有向無環圖的頂點上定義Sprague-Garundy函數。
首先定義mex(minimal excludant)運算,這是施加於一個集合的運算,表示最小的不屬於這個集合的非負整數。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
對於一個給定的有向無環圖,定義關於圖的每個頂點的Sprague-Garundy函數g如下:g(x)=mex{ g(y) | y是x的後繼 }。
來看一下SG函數的性質。首先,所有的terminal position所對應的頂點,也就是沒有出邊的頂點,其SG值爲0,因爲它的後繼集合是空集。

(pic1)
對於一個g(x)=0的頂點x,它的所有後繼y都滿足g(y)!=0。  N
對於一個g(x)!=0的頂點,必定存在一個後繼y滿足g(y)=0。  P

以上這三句話表明,頂點x所代表的postion是P-position當且僅當g(x)=0(跟P-positioin/N-position的定義的那三句話是完全對應的)。我們通過計算有向無環圖的每個頂點的SG值,就可以對每種局面找到必勝策略了。但SG函數的用途遠沒有這樣簡單。如果將有向圖遊戲變複雜一點,比如說,有向圖上並不是只有一枚棋子,而是有n枚棋子,每次可以任選一顆進行移動,這時,怎樣找到必勝策略呢?
讓我們再來考慮一下頂點的SG值的意義。當g(x)=k時,表明對於任意一個0<=i<k,都存在x的一個後繼y滿足g(y)=i。也就是說,當某枚棋子的SG值是k時,我們可以把它變成0、變成1、……、變成k-1,但絕對不能保持k不變。不知道你能不能根據這個聯想到Nim遊戲,Nim遊戲的規則就是:每次選擇一堆數量爲k的石子,可以把它變成0、變成1、……、變成k-1,但絕對不能保持k不變。這表明,如果將n枚棋子所在的頂點的SG值看作n堆相應數量的石子,那麼這個Nim遊戲的每個必勝策略都對應於原來這n枚棋子的必勝策略!
對於n個棋子,設它們對應的頂點的SG值分別爲(a1,a2,...,an),再設局面(a1,a2,...,an)時的Nim遊戲的一種必勝策略是把ai變成k,那麼原遊戲的一種必勝策略就是把第i枚棋子移動到一個SG值爲k的頂點。這聽上去有點過於神奇——怎麼繞了一圈又回到Nim遊戲上了。
其實我們還是隻要證明這種多棋子的有向圖遊戲的局面是P-position當且僅當所有棋子所在的位置的SG函數的異或爲0。這個證明與上節的Bouton's Theorem幾乎是完全相同的,只需要適當的改幾個名詞就行了。
剛纔,我爲了使問題看上去更容易一些,認爲n枚棋子是在一個有向圖上移動。但如果不是在一個有向圖上,而是每個棋子在其對應的有向圖上,每次可以任選一個棋子(也就是任選一個有向圖)進行移動,這樣也不會給結論帶來任何變化。
所以我們可以定義有向圖遊戲的和(Sum of Graph Games):設G1、G2、……、Gn是n個有向圖遊戲,定義遊戲G是G1、G2、……、Gn的和(Sum),遊戲G的移動規則是:任選一個子遊戲Gi並移動上面的棋子。Sprague-Grundy Theorem就是:g(G)=g(G1)^g(G2)^...^g(Gn)。也就是說,遊戲的和的SG函數值是它的所有子游戲的SG函數值的異或。
再考慮在本文一開頭的一句話:任何一個ICG都可以抽象成一個有向圖遊戲。所以“SG函數”和“遊戲的和”的概念就不是侷限於有向圖遊戲。我們給每個ICG的每個position定義SG值,也可以定義n個ICG的和。所以說當我們面對由n個遊戲組合成的一個遊戲時,只需對於每個遊戲找出求它的每個局面的SG值的方法,就可以把這些SG值全部看成Nim的石子堆,然後依照找Nim的必勝策略的方法來找這個遊戲的必勝策略了!(Nim其實就是n個從一堆中拿石子的遊戲求SG的變型,總SG=n個sg的異或)。(very
 important)
 

(pic 2) 注:此圖中值非SG值,%4後纔是SG值。SG的值根據mex函數確定,下面有具體實現方法(打表or DFS)。

回到本文開頭的問題。有n堆石子,每次可以從第1堆石子裏取1顆、2顆或3顆,可以從第2堆石子裏取奇數顆,可以從第3堆及以後石子裏取任意顆……我們可以把它看作3個子遊戲,第1個子遊戲只有一堆石子,每次可以取1、2、3顆,很容易(看pic2)看出x%4==0時處於P局面,即x顆石子的局面的SG值是x%4,(即把.中的值改成原值%4)。第2個子遊戲也是隻有一堆石子,每次可以取奇數顆,經過簡單的畫圖可以知道這個遊戲有x顆石子時的SG值是x%2。第3個遊戲有n-2堆石子,就是一個Nim遊戲。對於原遊戲的每個局面,把三個子游戲的SG值異或一下就得到了整個遊戲的SG值,然後就可以根據這個SG值判斷是否有必勝策略以及做出決策了。其實看作3個子遊戲還是保守了些,乾脆看作n個子遊戲,其中第1、2個子遊戲如上所述,第3個及以後的子游戲都是“1堆石子,每次取幾顆都可以”,稱爲“任取石子游戲”,這個超簡單的遊戲有x顆石子的SG值顯然就是x。其實,n堆石子的Nim遊戲本身不就是n個“任取石子游戲”的和嗎?
所以,對於我們來說,SG函數與“遊戲的和”的概念不是讓我們去組合、製造稀奇古怪的遊戲,而是把遇到的看上去有些複雜的遊戲試圖分成若干個子游戲,對於每個比原遊戲簡化很多的子游戲找出它的SG函數,然後全部異或起來就得到了原遊戲的SG函數,就可以解決原遊戲了。這種“分而治之”的思想在下一節介紹的“翻硬幣遊戲”中將被應用得淋漓盡致。

以下是本文最重要的部分:
解題模型:
1.把原遊戲分解成多個獨立的子游戲,則原遊戲的SG函數值是它的所有子游戲的SG函數值的異或。
       即sg(G)=sg(G1)^sg(G2)^...^sg(Gn)。
2.分別考慮沒一個子遊戲,計算其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)
{
    int i,j;
    memset(sg,0,sizeof(sg));
    for(i=1;i<=n;i++)
    {
        memset(hash,0,sizeof(hash));
        for(j=1;f[j]<=i;j++)
            hash[sg[i-f[j]]]=1;
        for(j=0;j<=n;j++)    //求mes{}中未出現的最小的非負整數
        {
            if(hash[j]==0)
            {
                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;
}

一般DFS只在打表解決不了的情況下用,首選打表預處理。

3.計算sg(G)=sg(G1)^sg(G2)^...^sg(Gn),

    sg(G)=0,即P-Position,即先手比敗。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章