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值。

3. 若sg(G)=0,则为P-Position(必败态),否则为N-Position(必胜态)。

 

SG值的计算方法:(重点)

1.可选步数为1~m的连续整数,直接取模即可,SG(x) = x % (m+1);

2.可选步数为任意步,SG(x) = x;

3.可选步数为一系列不一定连续的数,用模板计算(模板在下面模板题代码中)。

 

模板题:HDU 1536 -- S-Nim

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 }
View Code

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 }
View Code

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 }
View Code

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 }
View Code

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 }
View Code

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 }
View Code

 

posted @ 2018-03-03 22:54  _kangkang  阅读(1405)  评论(0编辑  收藏  举报