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");elseprintf("Nacci\n");}return 0; }總結
以上是生活随笔為你收集整理的Fibonacci again and again HDU - 1848(尼姆博弈+SG函数的运用+SG函数详解)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你的电脑内存是不是正确使用了你的电脑内存
- 下一篇: 戴尔笔记本睡眠无法唤醒如何操作戴尔笔记本