题意:
今天,又一个关于Fibonacci的题目出现了,它是一个小游戏,定义如下:
1、 这是一个二人游戏;
2、 一共有3堆石子,数量分别是m, n, p个;
3、 两人轮流走;
4、 每走一步可以选择任意一堆石子,然后取走f个;
5、 f只能是菲波那契数列中的元素(即每次只能取1,2,3,5,8…等数量);
6、 最先取光所有石子的人为胜者;
假设双方都使用最优策略,请判断先手的人会赢还是后手的人会赢。
解题:游戏和的SG函数等于各个游戏SG函数的Nim和。这样就可以将每一个子游戏分而治之,从而简化了问题。而Bouton定理就是Sprague-Grundy定理在Nim游戏中的直接应用,因为单堆的Nim游戏 SG函数满足 SG(x) = x。
SG函数:
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于任意状态 x , 定义 SG(x) = mex(S),其中 S 是 x 后继状态的SG函数值的集合。如 x 有三个后继状态分别为 SG(a),SG(b),SG(c),那么SG(x) = mex{SG(a),SG(b),SG(c)}。 这样 集合S 的终态必然是空集,所以SG函数的终态为 SG(x) = 0,当且仅当 x 为必败点P时。
然后用异或判断SG即可,如一开始有三堆,个数分别为a,b,c.则SG(a) xor SG(b) xor SG(c)==0则先手输,否则先手胜利
#include<cstdio>
#include<cstring>
#define N 1005
using namespace std;
int f[N],hash[N],sg[N];
void getsg(int n)
{
memset(sg,0,sizeof(sg));
for(int i=1;i<=n;i++)
{
memset(hash,0,sizeof(hash));
for(int j=1;f[j]<=i;j++)
hash[sg[i-f[j]]]=1;
for(int j=0;j<=n;j++)
{
if(!hash[j])
{
sg[i]=j;
break;
}
}
}
}
int main()
{
//freopen("t.txt","r",stdin);
f[1]=1;f[2]=2;
for(int i=3;i<=30;i++)
f[i]=f[i-1]+f[i-2];
getsg(1000);
int n,m,p;
while(scanf("%d%d%d",&n,&m,&p)!=EOF)
{
if(n+m+p==0) break;
if(sg[m]^sg[n]^sg[p]) printf("Fibo\n");
else printf("Nacci\n");
}
return 0;
}