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. 可选步数为1~m的连续整数,直接取模即可,SG(x) = x % (m+1);
  2. 可选步数为任意步,SG(x) = x;
  3. 可选步数为一系列不连续的数,用GetSG()计算

证明略(不会)

求SG值

1. 打表

//f[]: 可以取走的石子数量
//sg[]: 1~n的sg函数值
//vis[]: mex{}
void getSG(int n) {
memset(sg, , sizeof(sg));
for (int i = ; i <= n; i++) {
memset(vis, , sizeof(vis));
for (int j = ; f[j] <= i && j < maxm; j++)
vis[sg[i - f[j]]] = ;
for (int j = ;; j++) if (!vis[j]) { //最小的未出现的正整数
sg[i] = j;
break;
}
}
}

2. 记忆化搜索

//记忆化搜索
//f[]: 从小到大排序
//sg[]: 初始化为-1
//maxm,石子个数,集合的最大数量
int dp(int x)
{
if (sg[x] != -) return sg[x];
bool vis[maxn];
memset(vis, , sizeof(vis));
for (int i = ; i < maxm; i++)
{
if (f[i] <= x)
{
dp(x - f[i]);
vis[sg[x - f[i]]] = ;
}
}
for (int i = ;; 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、  最先取光所有石子的人为胜者;

假设双方都使用最优策略,请判断先手的人会赢还是后手的人会赢。

代码:

 #include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std; const int maxn = + ;
const int maxm = ; //石子个数 int f[maxm], sg[maxn];
bool vis[maxn];
//f[]: 可以取走的石子数量
//sg[]: 1~n的sg函数值
//vis[]: mex{}
void getSG(int n) {
memset(sg, , sizeof(sg));
for (int i = ; i <= n; i++) {
memset(vis, , sizeof(vis));
for (int j = ; f[j] <= i && j < maxm; j++)
vis[sg[i - f[j]]] = ;
for (int j = ;; j++) if (!vis[j]) { //最小的未出现的正整数
sg[i] = j;
break;
}
}
} //记忆化搜索
//f[]: 从小到大排序
//sg[]: 初始化为-1
//maxm,石子个数,集合的最大数量
int dp(int x)
{
if (sg[x] != -) return sg[x];
bool vis[maxn];
memset(vis, , sizeof(vis));
for (int i = ; i < maxm; i++)
{
if (f[i] <= x)
{
dp(x - f[i]);
vis[sg[x - f[i]]] = ;
}
}
for (int i = ;; i++)
{
if (!vis[i]) return sg[x] = i;
}
} void init()
{
f[] = f[] = ;
for (int i = ; i < maxm; i++)
f[i] = f[i - ] + f[i - ];
memset(sg, -, sizeof(sg));
} int m, n, p; int main()
{
init();
//getSG(1000);
while (scanf("%d%d%d", &n, &m, &p) == && n)
{
if (dp(m) ^ dp(n) ^ dp(p)) printf("Fibo\n");
else printf("Nacci\n");
}
return ;
}

参考链接:

1、https://blog.csdn.net/yizhangbiao/article/details/51992022

2、https://blog.csdn.net/strangedbly/article/details/51137432

最新文章

  1. 数组为什么可以使用linq查询
  2. MySQL的表使用
  3. 【线段树区间合并】HDU1540-Tunnel Warfare
  4. jffs2文件系统制作
  5. 设计模式之美:Structural Patterns(结构型模式)
  6. Stationary point
  7. lightoj1074 最短路
  8. iOS开发基础之ivars(实例变量)与@property(属性)
  9. 设置repeater每行多少个的方法
  10. 【Android 应用开发】 ActionBar 样式详解 -- 样式 主题 简介 Actionbar 的 icon logo 标题 菜单样式修改
  11. lighttpd配置虚拟主机/php等WEB环境
  12. 使用bootstrap建立响应式网页——头部导航栏
  13. Hibernate异常之Integer转float(自动类型转换错误)
  14. float与double
  15. 有哪些api接口可以实现微信自动唤醒浏览器,下载app,打开网页
  16. UnderWater+SDN论文之六
  17. 关于指针*前后const代表的意思——记住一个口诀“左内右本”
  18. GUC-14 ForkJoin
  19. NodeJS错误处理最佳实践
  20. 线程的同步之Synchronized的使用

热门文章

  1. spring-boot-starter-data-redis学习笔记01
  2. 《剑指offer》面试题8—旋转数组的最小数字
  3. 当我们聊kubernetes operator时,我们在聊些什么
  4. 51nod1127(尺取法)
  5. spring-eureka 源码解读----作为集群的eureka怎么样实现不做二次传播
  6. html表格合并单元格的运用实例
  7. Python-10-条件和条件语句
  8. Python web前端 08 字符串 数组 json
  9. 072 Edit Distance 编辑距离
  10. dede用户登录时,跳转到提示页时报404错误