SG函数
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 有三个后继状态分别为 a, b, 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 }