[hdu1848]Fibonacci again and again——SG函数,SG定理入门 大佬们的博客 Some Links

Description:

任何一个大学生对菲波那契数列(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、 最先取光所有石子的人为胜者;

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

思路:

其实刚刚接触SG函数做这个题目挺好的。SG函数就是一个DAG上的函数,每一个点表示当前局面的一种状态,然后往后连的边表示这个状态的转移,在博弈里面这样挺形象的。
我们如何分析是否先手必胜呢?

  1. 考虑整个博弈的逆过程,如果先手的状态就直接和终止状态有连边,那么无疑先手必胜。
  2. 如果有一个状态没有一个后继和先手必胜的点有连边的话,那么这个点必定先手必败。
  3. 然后我们通过数学归纳可以发现,一个点先手必胜,则后面只要有一条连接先手必败点就可以了。(博弈主角超级高的智商可以主动选择这个点,之后那个人的选择由于是必败点,所有又只可以选择必胜的点,然后就可以这样一直到达终点。)
  4. 同理,没有一条边连向必胜点的点为必败点。
    然后这就是SG函数的低级版。

SG函数:

定义: S G [ x ] x 的后继节点的 S G 值补集中的最小值。
然后这样值满足上面的条件的,为0的点为必败,其余为必胜。
然后会有一条优美的定理:当多个游戏叠加的时候,游戏的SG函数和等于所有子游戏的SG函数值得异或和。
证明可以这样:
终点的异或和为0,当目前异或和为0时,每次改动一个点必定会有1的出现。
当前的异或和为1时,必定存在一种方法使得为0。方法:记最后的和的位数为1的最高位为k,然后找到一个这一位为1的SG函数,改变状态,就可全部把1消掉了。
然后这题就把SG函数求出来在SG定理搞一波就没了。

/*=========================
 * Author : ylsoi
 * Problem : hdu1848
 * Algorithm : SG
 * Time : 2018.6.1
 * ======================*/
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<climits>
using namespace std;
void File(){
    freopen("hdu1848.in","r",stdin);
    freopen("hdu1848.out","w",stdout);
}
template<typename T>bool chkmax(T &_,T __){return _<__ ? (_=__,1) : 0;}
template<typename T>bool chkmin(T &_,T __){return _>__ ? (_=__,1) : 0;}
#define REP(i,a,b) for(register int i=a;i<=b;++i)
#define DREP(i,a,b) for(register int i=a;i>=b;--i)
#define MREP(i,x) for(register int i=beg[x];i;i=E[i].last)
#define mem(a) memset(a,0,sizeof(a))
#define ll long long
#define inf INT_MAXj
const int maxn=1000+10;
int n,m,p,f[maxn],SG[maxn];
bool in[maxn];
int main(){
    File();
    f[1]=1;
    f[2]=2;
    REP(i,3,20)f[i]=f[i-1]+f[i-2];
    REP(i,1,1000){
        mem(in);
        REP(j,1,20){
            if(f[j]>i)break;
            in[SG[i-f[j]]]=1;
        }
        REP(j,0,1000)if(!in[j]){
            SG[i]=j;
            break;
        }
    }
    while(1){
        scanf("%d%d%d",&n,&m,&p);
        if(!n && !m && !p)break;
        if(SG[n]^SG[m]^SG[p])puts("Fibo");
        else puts("Nacci");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/ylsoi/article/details/80542238
今日推荐