• 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
    作者:_kangkang
    本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
  • 相关阅读:
    [BZOJ1385][Baltic2000]Division expression
    [BZOJ1412/Luogu2598][ZJOI2009]狼和羊的故事
    iPhone SlideShow
    替换一个文件中的内容BAT
    用指定字符串替换指定内容
    修改注册表
    如何在单独的窗口中打开 Excel 文件
    IBatis和Hibernate区别
    c# 常用的面试题
    在线编译器
  • 原文地址:https://www.cnblogs.com/kangkang-/p/8495659.html
Copyright © 2020-2023  润新知