SG函數
有個講得不錯的博客:http://blog.csdn.net/strangedbly/article/details/51137432
簡單介紹:
Sprague-Grundy定理(SG定理):
游戲和的SG函數等於各個游戲SG函數的Nim和。這樣就可以將每一個子游戲分而治之,從而簡化了問題。而Bouton定理就是Sprague-Grundy定理在Nim游戲中的直接應用,因為單堆的Nim游戲 SG函數滿足 SG(x) = x。
SG函數:
首先定義mex(minimal excludant)運算,這是施加於一個集合的運算,表示最小的不屬於這個集合的非負整數。例如mex{0,1,2,4}=3、mex{2,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時。
解題模型:
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.可選步數為一系列不一定連續的數,用模板計算(模板在下面模板題代碼中)。
AC代碼+詳細注釋:

1 #include <iostream> 2 #include <algorithm> 3 #include <cstring> 4 #define MAXN 10010 // 最大堆數 5 #define MAXM 110 // 最多有MAXM種不同個數的取石子方法 6 using namespace std; 7 int f[MAXM]; // f為可取石子數的集合 8 int sg[MAXN]; // sg[i]表示石子數為i時的sg函數值 9 bool Hash[MAXN]; // 標記一個數是否在mex{}集合中出現 10 // 打表預處理sg數組 11 void getSG(int m) { 12 memset(sg, 0, sizeof(sg)); 13 for (int i = 1; i < MAXN; i++) { 14 memset(Hash, false, sizeof(Hash)); 15 for (int j = 0; j < m && f[j] <= i; j++) 16 Hash[sg[i-f[j]]] = true; // 當前石子數為i,i-f[i]表示由i所能達到的石子數,將其sg值標記為已出現 17 for (int j = 0; j < MAXN; j++) { // mex(minimal excludant)運算 18 if (!Hash[j]) { 19 sg[i] = j; 20 break; 21 } 22 } 23 } 24 } 25 // 加一個dfs預處理sg數組,注意sg數組需要初始化為-1,而上面打表解法需要初始化為0 26 // 一般首選打表預處理,難以打表才用dfs 27 int SG_dfs(int x) { 28 if (sg[x] != -1) return sg[x]; 29 memset(Hash, false, sizeof(Hash)); 30 for (int i = 0; i < m && f[i] <= x; i++) { // m為集合f的大小 31 SG_dfs(x - f[i]); 32 Hash[sg[x-f[i]]] = true; 33 } 34 for (int i = 0; i < MAXN; i++) { 35 if (!Hash[i]) { 36 return sg[x] = i; 37 } 38 } 39 } 40 41 int main() { 42 int n, m; 43 while (cin >> m && m) { 44 for (int i = 0; i < m; i++) cin >> f[i]; 45 sort(f, f + m); 46 getSG(m); 47 cin >> n; 48 while (n--) { 49 int num, sum = 0; 50 cin >> num; 51 for (int i = 0; i < num; i++) { 52 int each; cin >> each; 53 sum ^= sg[each]; 54 } 55 if (sum) cout << 'W'; 56 else cout << 'L'; 57 } 58 cout << endl; 59 } 60 return 0; 61 }
HDU 1517 -- A Multiplication Game (典型的有向圖游戲):

1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 #include <map> 5 using namespace std; 6 typedef long long LL; 7 map<LL, int> mp; 8 LL n; 9 10 int sg(LL x) { 11 if (x >= n) return 0; 12 if (mp.count(x)) return mp[x]; 13 vector<LL> mex; 14 for (int i = 2; i <= 9; i++) { 15 mex.push_back(sg(x * i)); 16 } 17 sort(mex.begin(), mex.end()); 18 int len = mex.size(); 19 for (int i = 0; i < len; i++) { 20 if (i != mex[i]) return mp[x] = i; 21 } 22 return mp[x] = mex[len-1] + 1; 23 } 24 25 int main() { 26 while (cin >> n) { 27 mp.clear(); 28 if (sg(1ll)) cout << "Stan wins." << endl; 29 else cout << "Ollie wins." << endl; 30 } 31 return 0; 32 }
HDU 1847 -- Good Luck in CET-4 Everybody! (簡單求sg):

1 #include <iostream> 2 #include <cstring> 3 #define MAXN 1010 4 #define MAXM 11 5 using namespace std; 6 int sg[MAXN], f[MAXM]; 7 bool Hash[MAXN]; 8 9 void getSG(int m) { 10 memset(sg, 0, sizeof(sg)); 11 for (int i = 1; i < MAXN; i++) { 12 memset(Hash, false, sizeof(Hash)); 13 for (int j = 0; j < m && f[j] <= i; j++) 14 Hash[sg[i-f[j]]] = true; 15 for (int j = 0; j < MAXN; j++) { 16 if (!Hash[j]) { 17 sg[i] = j; 18 break; 19 } 20 } 21 } 22 } 23 24 int main() { 25 int n, num = 1; 26 for (int i = 0; i < MAXM; num <<= 1, i++) f[i] = num; 27 getSG(MAXM); 28 while (cin >> n) { 29 if (sg[n]) cout << "Kiki" << endl; 30 else cout << "Cici" << endl; 31 } 32 return 0; 33 }
HDU 1848 -- Fibonacci again and again (分為三個子游戲,求原游戲sg值):

1 #include <iostream> 2 #include <cstring> 3 #define MAXN 1010 4 #define MAXM 100 5 using namespace std; 6 int sg[MAXN], f[MAXM]; 7 bool Hash[MAXN]; 8 9 int getFib() { 10 int i; 11 f[0] = 1, f[1] = 2; 12 for (i = 2; f[i] <= MAXN; i++) f[i] = f[i-1] + f[i-2]; 13 return i; 14 } 15 void getSG(int m) { 16 memset(sg, 0, sizeof(sg)); 17 for (int i = 1; i < MAXN; i++) { 18 memset(Hash, false, sizeof(Hash)); 19 for (int j = 0; j < m && f[j] <= i; j++) 20 Hash[sg[i-f[j]]] = true; 21 for (int j = 0; j < MAXN; j++) { 22 if (!Hash[j]) { 23 sg[i] = j; 24 break; 25 } 26 } 27 } 28 } 29 30 int main() { 31 int a, b, c; 32 getSG(getFib()); 33 while (cin >> a >> b >> c && (a || b || c)) { 34 if (sg[a] ^ sg[b] ^ sg[c]) cout << "Fibo" << endl; 35 else cout << "Nacci" << endl; 36 } 37 return 0; 38 }
HDU 1079 -- Calendar Game (細致模擬):

1 #include <iostream> 2 #include <cstring> 3 using namespace std; 4 int sg[110][15][40]; 5 int day[110]; 6 int mm[15] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 7 8 int get_sg(int y, int m, int d) { 9 if (sg[y][m][d] != -1) return sg[y][m][d]; 10 if (m < 12 && d == mm[m] && d > mm[m+1]) { 11 if (get_sg(y, m + 1, 1)) return sg[y][m][d] = 0; 12 else return sg[y][m][d] = 1; 13 } 14 int a, b; 15 if (m == 2 && d == day[y] || m < 12 && d == mm[m] && d <= mm[m+1]) { 16 a = get_sg(y, m + 1, d), b = get_sg(y, m + 1, 1); 17 } 18 else if (m == 2 && d < day[y] || m < 12 && d < mm[m]) { 19 a = get_sg(y, m, d + 1), b = get_sg(y, m + 1, d); 20 } 21 else if (m == 12 && d < mm[12]) { 22 a = get_sg(y + 1, 1, d), b = get_sg(y, m, d + 1); 23 } 24 else if (m == 12 && d == mm[12]) { 25 a = get_sg(y + 1, 1, d), b = (y + 1, 1, 1); 26 } 27 if (a == 0 || b == 0) return sg[y][m][d] = 1; 28 else return sg[y][m][d] = 0; 29 } 30 31 int main() { 32 int y, m, d, t; 33 memset(sg, -1, sizeof(sg)); 34 for (int i = 1900; i <= 2001; i++) { 35 if (i % 4 == 0 && i % 100 || i % 400 == 0) day[i-1900] = 29; 36 else day[i-1900] = 28; 37 } 38 sg[101][11][4] = 0; 39 for (int i = 5; i <= mm[11]; i++) sg[101][11][i] = 1; 40 for (int i = 1; i <= mm[12]; i++) sg[101][12][i] = 1; 41 cin >> t; 42 while (t--) { 43 cin >> y >> m >> d; 44 y -= 1900; 45 if (get_sg(y, m, d)) cout << "YES" << endl; 46 else cout << "NO" << endl; 47 } 48 return 0; 49 }
HDU 1404 -- Digital Deletions (暴力打sg表):

1 #include <stdio.h> 2 #include <cstring> 3 #define N 1000000 4 using namespace std; 5 bool sg[N]; 6 7 int get_len(int n) { 8 int k = 1; 9 while (n /= 10) k++; 10 return k; 11 } 12 void get_sg(int n) { 13 int len = get_len(n); 14 int base = 1; 15 for (int i = 0; i < len; i++) { // 將某一位變大都為必勝態 16 int m = n; 17 int tem = (m % (10 * base)) / base; 18 m -= tem * base; 19 for (int j = tem + 1; j <= 9; j++) sg[m+j*base] = true; 20 base *= 10; 21 } 22 base = 1; 23 while (len++ < 6) { // n后面補0...都為必勝態 24 n *= 10; 25 for (int i = 0; i < base; i++) sg[n+i] = true; 26 base *= 10; 27 } 28 } 29 30 int main() { 31 memset(sg, false, sizeof(sg)); 32 // 1為必敗態,能一步到達必敗態的都為必勝態,不能一步到達必敗態的都為必敗態 33 for (int i = 1; i < N; i++) if (!sg[i]) get_sg(i); 34 char s[10]; 35 while (gets(s)) { 36 if (s[0] == '0') puts("Yes"); 37 else { 38 int n = 0; 39 for (int i = 0; s[i]; i++) n = n * 10 + s[i] - '0'; 40 if (sg[n]) puts("Yes"); 41 else puts("No"); 42 } 43 } 44 return 0; 45 }