sg函数模板

sg[i]为0表示i节点先手必败。

首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数

例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。

对于一个给定的有向无环图,定义关于图的每个顶点的Sprague-Grundy函数g如下:g(x)=mex{ g(y) | y是x的后继 },这里的g(x)即sg[x]

例如:取石子问题,有1堆n个的石子,每次只能取{1,3,4}个石子,先取完石子者胜利,那么各个数的SG值为多少?

sg[0]=0,f[]={1,3,4},

x=1时,可以取走1-f{1}个石子,剩余{0}个,mex{sg[0]}={0},故sg[1]=1;

x=2时,可以取走2-f{1}个石子,剩余{1}个,mex{sg[1]}={1},故sg[2]=0;

x=3时,可以取走3-f{1,3}个石子,剩余{2,0}个,mex{sg[2],sg[0]}={0,0},故sg[3]=1;

x=4时,可以取走4-f{1,3,4}个石子,剩余{3,1,0}个,mex{sg[3],sg[1],sg[0]}={1,1,0},故sg[4]=2;

x=5时,可以取走5-f{1,3,4}个石子,剩余{4,2,1}个,mex{sg[4],sg[2],sg[1]}={2,0,1},故sg[5]=3;

以此类推.....

   x         0  1  2  3  4  5  6  7  8....

sg[x]      0  1  0  1  2  3  2  0  1....

计算从1-n范围内的sg值,

s[]存储可以走的步数,s[0]表示可以有多少种走法

s[]需要从小到大排序

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

2.可选步数为任意步,sg[x] = x;

3.可选步数为一系列不连续的数,用sg函数计算

模板1(dfs):

 1 /*
 2 s数组表示合法移动集合,从小到大排序。sNum合法移动个数
 3 sg数组初始化为-1,对每个集合s仅需初始化1次
 4 */
 5 const int MAXN = 20;//s集合大小
 6 const int MAXM = 1000 + 5;//
 7 int s[MAXN], sNum;
 8 int sg[MAXM];
 9 
10 int dfsSg(int x)
11 {
12     if (sg[x] != -1) {
13         return sg[x];
14     }
15     int i;
16     bool vis[MAXN];//sg值小于等于合法移动个数sNum
17 
18     memset(vis, false, sizeof(vis));
19     for (i = 0; i < sNum && s[i] <= x; ++i) {
20         dfsSg(x - s[i]);
21         vis[sg[x - s[i]]] = true;
22     }
23     for (i = 0; i <= sNum; ++i) {
24         if (!vis[i]) {
25             sg[x] = i;
26             break;
27         }
28     }
29     return sg[x];
30 }
View Code

模板2(打表):

求出所有sg值,有时没必要,用dfs就行

 1 /*
 2 s数组表示合法移动集合,从小到大排序。sNum合法移动个数
 3 sg值对每个集合s仅需求一次
 4 */
 5 const int MAXN = 20;//s集合大小
 6 const int MAXM = 1000 + 5;//
 7 int s[MAXN], sNum;
 8 int sg[MAXM];
 9 bool exist[MAXN];//sg值小于等于合法移动个数sNum
10 
11 void getSg(int n)
12 {
13     int i, j;
14     sg[0] = 0;//必败态
15     for (i = 1; i <= n; ++i) {
16         memset(exist, false, sizeof(exist));
17         for (j = 0; j < sNum && s[j] <= i; ++j) {
18             exist[sg[i - s[j]]] = true;
19         }
20         for (j = 0; j <= sNum; ++j) {
21             if (!exist[j]) {
22                 sg[i] = j;
23                 break;
24             }
25         }
26     }
27 }
View Code

hdu 1848 Fibonacci again and again(sg)

取石子问题,一共有3堆石子,每次只能取斐波那契数个石子,先取完石子者胜利,问先手胜还是后手胜

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 /*
 5 s数组表示合法移动集合,从小到大排序。sNum合法移动个数
 6 sg数组初始化为-1,对每个集合s仅需初始化1次
 7 */
 8 const int MAXN = 20;//s集合大小
 9 const int MAXM = 1000 + 5;//
10 int s[MAXN], sNum;
11 int sg[MAXM];
12 
13 int dfsSg(int x)
14 {
15     if (sg[x] != -1) {
16         return sg[x];
17     }
18     int i;
19     bool vis[MAXN];//sg值小于等于合法移动个数sNum
20 
21     memset(vis, false, sizeof(vis));
22     for (i = 0; i < sNum && s[i] <= x; ++i) {
23         dfsSg(x - s[i]);
24         vis[sg[x - s[i]]] = true;
25     }
26     for (i = 0; i <= sNum; ++i) {
27         if (!vis[i]) {
28             sg[x] = i;
29             break;
30         }
31     }
32     return sg[x];
33 }
34 
35 int main()
36 {
37     int i;
38     s[0] = 1;
39     s[1] = 2;
40     for (i = 2; i < MAXN; ++i) {
41         s[i] = s[i - 1] + s[i - 2];
42         //printf("%d %d
", i, s[i]);
43     }
44     sNum = 16;
45     int m, n, p;
46     int sum;
47     memset(sg, -1, sizeof(sg));
48     while (~scanf("%d%d%d", &m, &n, &p)) {
49         if (m == 0 && n == 0 && p == 0) {
50             break;
51         }
52         dfsSg(m);
53         dfsSg(n);
54         dfsSg(p);
55         sum = sg[m] ^ sg[n] ^ sg[p];
56         if (sum != 0) {
57             printf("Fibo
");
58         } else {
59             printf("Nacci
");
60         }
61     }
62     return 0;
63 }
View Code
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 /*
 5 s数组表示合法移动集合,从小到大排序。sNum合法移动个数
 6 sg值对每个集合s仅需求一次
 7 */
 8 const int MAXN = 20;//s集合大小
 9 const int MAXM = 1000 + 5;//
10 int s[MAXN], sNum;
11 int sg[MAXM];
12 bool exist[MAXN];//sg值小于等于合法移动个数sNum
13 
14 void getSg(int n)
15 {
16     int i, j;
17     sg[0] = 0;//必败态
18     for (i = 1; i <= n; ++i) {
19         memset(exist, false, sizeof(exist));
20         for (j = 0; j < sNum && s[j] <= i; ++j) {
21             exist[sg[i - s[j]]] = true;
22         }
23         for (j = 0; j <= sNum; ++j) {
24             if (!exist[j]) {
25                 sg[i] = j;
26                 break;
27             }
28         }
29     }
30 }
31 
32 int main()
33 {
34     int i;
35     s[0] = 1;
36     s[1] = 2;
37     for (i = 2; i < MAXN; ++i) {
38         s[i] = s[i - 1] + s[i - 2];
39         //printf("%d %d
", i, s[i]);
40     }
41     sNum = 16;
42     int m, n, p;
43     int sum;
44     getSg(1000);
45     while (~scanf("%d%d%d", &m, &n, &p)) {
46         if (m == 0 && n == 0 && p == 0) {
47             break;
48         }
49         sum = sg[m] ^ sg[n] ^ sg[p];
50         if (sum != 0) {
51             printf("Fibo
");
52         } else {
53             printf("Nacci
");
54         }
55     }
56     return 0;
57 }
View Code
原文地址:https://www.cnblogs.com/gongpixin/p/5506497.html