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

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\n", 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\n");

58 } else {

59 printf("Nacci\n");

60 }

61 }

62 return 0;

63 }

View Code

 

1 #include

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\n", 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\n");

52 } else {

53 printf("Nacci\n");

54 }

55 }

56 return 0;

57 }

View Code

 

好文阅读

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。