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
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)等於最小的不屬於
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,那麼原遊戲的一種必勝策略
這就好像我們敲程式碼,我們通過高階程式語言來間接寫機器語言執行計算機。
計算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;
}
}
}
給出一道入門級的題目:
#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;
}
我的標籤:做個有情懷的程式設計師。