Fibonacci again and again HDU - 1848(尼姆博弈+SG函数的运用+SG函数详解)

题意:

给出三堆石子(m,n,p个),两人每次只能取斐波那契数f[i]个,最先取光所有石子者取胜

题目:

任何一个大学生对菲波那契数列(Fibonacci numbers)应该都不会陌生,它是这样定义的:
F(1)=1;
F(2)=2;
F(n)=F(n-1)+F(n-2)(n>=3);
所以,1,2,3,5,8,13……就是菲波那契数列。
在HDOJ上有不少相关的题目,比如1005 Fibonacci again就是曾经的浙江省赛题。
今天,又一个关于Fibonacci的题目出现了,它是一个小游戏,定义如下:
1、 这是一个二人游戏;
2、 一共有3堆石子,数量分别是m, n, p个;
3、 两人轮流走;
4、 每走一步可以选择任意一堆石子,然后取走f个;
5、 f只能是菲波那契数列中的元素(即每次只能取1,2,3,5,8…等数量);
6、 最先取光所有石子的人为胜者;

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

Input

输入数据包含多个测试用例,每个测试用例占一行,包含3个整数m,n,p(1<=m,n,p<=1000)。
m=n=p=0则表示输入结束。

Output

如果先手的人能赢,请输出“Fibo”,否则请输出“Nacci”,每个实例的输出占一行。

Sample Input

1 1 1
1 4 1
0 0 0

Sample Output

Fibo
Nacci

分析:

SG函数解组合游戏
SG定理: 游戏“和”的SG函数等于各子游戏SG函数的Nim和。

所谓的SG值就是记录当前状态是N是P的具体值,N-position表示必赢状态(其SG值不为0),P-position表示必输状态(其SG值为0)。
下面介绍怎么求SG值:首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示不属于mex这个集合的最小非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。对于一个给定的有向无环图,定义关于图的每个顶点的Sprague-Grundy函数g如下:
g(n)=mex{ g(m) | m是n的后继 },这里的g(n)即dp[n]
拿本题的栗子来讲:首先有dp[0]=0,f[]={1,2,3,5…};(f数组存放可以抓走石子的个数(斐波那契数列),并且按升序存放)
举某一堆的例子。
当n=1时,先手可以抓走1-f{1}个石子,剩余{0}张,mex{dp[0]}={0},故dp[1]=1;
当n=2时,先手可以抓走2-f{1,2}个石子,剩余{1,0}个石子,mex{dp[1],dp[0]}={1,0},故dp[2]=2;
当n=3时,先手可以抓走3-f{1,2,3}个石子,剩余{2,1,0}个石子,mex{dp[2],dp[1],dp[0]}={2,1,0},故dp[3]=3;
当n=4时,先手可以抓走4-f{1,2,3}个石子,剩余{3,2,1}个石子,mex{dp[3],dp[2],dp[1]}={3,2,1},故dp[4]=0;
当n=5时,先手可以抓走5-f{1,2,3,5}个石子,剩余{4,3,2,0}个石子,mex{dp[4],dp[3],dp[2],dp[0]}={0,3,2,0},故dp[5]=1;
以此类推…
n 0 1 2 3 4 5 6 7 8 9…
dp[n] 0 1 2 3 0 1 2 3 4 5…
由上述实例我们就可以得到1~n的SG值的计算步骤,如下所示:
①、使用f数组保存菲波那契数列。
②、然后使用book数组来标记当前状态n的后继m状态。
③、最后模拟mex运算,也就是我们在集合mex中查找未被标记值的最小值,将其赋值给dp(n)。
④、不断的重复 ② - ③ 的步骤,即可完成计算1~n的SG值。

关于3种SG值计算方法(重点):
1、可选步数为1~m的连续整数,直接取模即可,SG(x) = x % (m+1);
2、可选步数为任意步,dp(x) = x;
3、可选步数为一系列不连续的数,用get_SG()计算
此题就是选取第3种方法来计算SG值。
若还不清楚,下面的代码有步骤详解:

AC代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int M=1e3+10;
int f[M],dp[M],book[M];
int m,n,p;
/**
SG值:一个点的SG值就是一个不等于它的后继点的SG的且大于等于零的最小整数。
同mex()函数。简单点来讲就是当前状态离最近一个必败点的距离。距离为0就是必败点
SG(x)=mex(S),S是x的后继状态的SG函数值集合,mex(S)表示不在S内的最小非负整数
SG值是P/N状态的具体化
*/
int main()
{
    f[0]=1;
    f[1]=2;
    for(int i=2; i<100; i++)////初始化
        f[i]=f[i-1]+f[i-2];
    for(int i=1; i<M; i++)///SG函数的运用;
    {
        memset(book,0,sizeof(book));///每轮到当前i就重新初始化book都为未访问状态,找出不属于这个集合的最小非负整数
        for(int j=0; f[j]<=i; j++)///此时j值不会越界,所以不考虑越界约束
            book[dp[i-f[j]]]=1;///i-f[j]为后继状态,book[sg[i-f[j]]]收录mex集合
        for(int j=0; j<M; j++)///求没有出现在mex集合中的非负最小值
            if(!book[j])
            {
                dp[i]=j;
                break;//每次break退出时就取不属于mex集合的最小非负整数
            }
    }
    while(~scanf("%d%d%d",&n,&m,&p)&&(n||m||p))
    {
        if(dp[m]^dp[n]^dp[p])///  对于每个堆的SG函数值,我们只需要异或判断结果,当dp[n]不为0时,即为N-position,此时先手必赢;
            printf("Fibo\n");
        else
            printf("Nacci\n");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zeng_jun_yv/article/details/105104972