SG函数入门&&HDU 1848
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}个石子,剩余{0}个,mex{sg[0]}=mex{0},故sg[1]=1;
x=2时,可以取走{1}个石子,剩余{1}个,mex{sg[1]}=mex{1},故sg[2]=0;
x=3时,可以取走{1,3}个石子,剩余{2,0}个,mex{sg[2],sg[0]}=mex{0,0},故sg[3]=1;
x=4时,可以取走{1,3,4}个石子,剩余{3,1,0}个,mex{sg[3],sg[1],sg[0]}=mex{1,1,0},故sg[4]=2;
x=5时,可以取走{1,3,4}个石子,剩余{4,2,1}个,mex{sg[4],sg[2],sg[1]}=mex{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值。
f(存储可以走的步数,f[0]表示可以有多少种走法)
f[]需要从小到大排序
- 可选步数为1~m的连续整数,直接取模即可,SG(x) = x % (m+1);
- 可选步数为任意步,SG(x) = x;
- 可选步数为一系列不连续的数,用GetSG()计算
证明略(不会)
求SG值
1. 打表
//f[]: 可以取走的石子数量 //sg[]: 1~n的sg函数值 //vis[]: mex{} void getSG(int n) { memset(sg, 0, sizeof(sg)); for (int i = 1; i <= n; i++) { memset(vis, 0, sizeof(vis)); for (int j = 0; f[j] <= i && j < maxm; j++) vis[sg[i - f[j]]] = 1; for (int j = 0;; j++) if (!vis[j]) { //最小的未出现的正整数 sg[i] = j; break; } } }
2. 记忆化搜索
//记忆化搜索 //f[]: 从小到大排序 //sg[]: 初始化为-1 //maxm,石子个数,集合的最大数量 int dp(int x) { if (sg[x] != -1) return sg[x]; bool vis[maxn]; memset(vis, 0, sizeof(vis)); for (int i = 0; i < maxm; i++) { if (f[i] <= x) { dp(x - f[i]); vis[sg[x - f[i]]] = 1; } } for (int i = 0;; i++) { if (!vis[i]) return sg[x] = i; } }
HDU 1848
今天,又一个关于Fibonacci的题目出现了,它是一个小游戏,定义如下:
1、 这是一个二人游戏;
2、 一共有3堆石子,数量分别是m, n, p个;
3、 两人轮流走;
4、 每走一步可以选择任意一堆石子,然后取走f个;
5、 f只能是菲波那契数列中的元素(即每次只能取1,2,3,5,8…等数量);
6、 最先取光所有石子的人为胜者;
假设双方都使用最优策略,请判断先手的人会赢还是后手的人会赢。
代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<cmath> 4 #include<algorithm> 5 using namespace std; 6 7 const int maxn = 1000 + 10; 8 const int maxm = 20; //石子个数 9 10 int f[maxm], sg[maxn]; 11 bool vis[maxn]; 12 //f[]: 可以取走的石子数量 13 //sg[]: 1~n的sg函数值 14 //vis[]: mex{} 15 void getSG(int n) { 16 memset(sg, 0, sizeof(sg)); 17 for (int i = 1; i <= n; i++) { 18 memset(vis, 0, sizeof(vis)); 19 for (int j = 0; f[j] <= i && j < maxm; j++) 20 vis[sg[i - f[j]]] = 1; 21 for (int j = 0;; j++) if (!vis[j]) { //最小的未出现的正整数 22 sg[i] = j; 23 break; 24 } 25 } 26 } 27 28 //记忆化搜索 29 //f[]: 从小到大排序 30 //sg[]: 初始化为-1 31 //maxm,石子个数,集合的最大数量 32 int dp(int x) 33 { 34 if (sg[x] != -1) return sg[x]; 35 bool vis[maxn]; 36 memset(vis, 0, sizeof(vis)); 37 for (int i = 0; i < maxm; i++) 38 { 39 if (f[i] <= x) 40 { 41 dp(x - f[i]); 42 vis[sg[x - f[i]]] = 1; 43 } 44 } 45 for (int i = 0;; i++) 46 { 47 if (!vis[i]) return sg[x] = i; 48 } 49 } 50 51 52 void init() 53 { 54 f[0] = f[1] = 1; 55 for (int i = 2; i < maxm; i++) 56 f[i] = f[i - 1] + f[i - 2]; 57 memset(sg, -1, sizeof(sg)); 58 } 59 60 int m, n, p; 61 62 int main() 63 { 64 init(); 65 //getSG(1000); 66 while (scanf("%d%d%d", &n, &m, &p) ==3 && n) 67 { 68 if (dp(m) ^ dp(n) ^ dp(p)) printf("Fibo\n"); 69 else printf("Nacci\n"); 70 } 71 return 0; 72 }
参考链接:
1、https://blog.csdn.net/yizhangbiao/article/details/51992022
2、https://blog.csdn.net/strangedbly/article/details/51137432