HDU 2516 取石子游戲(Fibonacci博弈)
問題描述
取石子游戲
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 8263 Accepted Submission(s): 5021
Problem Description
1堆石子有n個,兩人輪流取.先取者第1次可以取任意多個,但不能全部取完.以後每次取的石子數不能超過上次取子數的2倍。取完者勝.先取者負輸出"Second win".先取者勝輸出"First win".
Input
輸入有多組.每組第1行是2<=n<2^31. n=0
Output
先取者負輸出"Second win". 先取者勝輸出"First win".
參看Sample Output.
Sample Input
2
13
10000
0
Sample Output
Second win
Second win
First win
------------------------------------------------------------
思路
斐波那契博弈,基於Zeckendorf定理。
定理1.(Zeckendorf定理):任意一個正整數都能分解成若干個不連續的Fibonacci數之和。(證明略)
首先給出結論:後手必勝的充要條件是n為Fibonacci數。
定理2.(充分性的證明):
數學歸納法。n = 2,先手只能取1個,後手勝。
對於任意n’> 2, n’= Fib(k) = Fib(k-1) + Fib(k-2), Fib(k-1) < 2 * Fib(k-2). 如果先手取的個數大於等於Fib(k-2),則後手一次可以取完餘下所有,後手勝。故只需考慮先手取x個,x = 1,2,…,Fib(k-2)-1個的情形。此時後手的策略是取y = Fib(k-2)-x個,y <= Fib(k-2) – 1,即取完Fib(k-2), 令問題演化為先手面對Fib(k-1)的問題。顯然 y < 1/2 * Fib(k-1),先手面對Fib(k-1)時,第一次可以取1,2,…,2*y < Fib(k-1)個,這是Fib(k-1)時原問題的弱問題(原問題為先手第一次可以取1,2,…,Fib(k-1)-1個),由歸納法知後手勝。
由此,我們歸納證明了n為Fibonacci數時後手必勝。
定理3.(必要性的證明,即證若n不是Fibonacci數,則先手必勝):
由Zeckendorf定理,給定正整數n,有 n = Fib(a[1]) + Fib(a[2]) + … + Fib(a[k]), a[1] > a[2] + 1 > a[3] + 2 > … > a[k] + k-1. 當k > 1時,先手的必勝策略為第一次拿走Fib(ak), 那麼根據(1) Fibonacci數列的性質 以及(2) a[k-1] > a[k]+1, 可得 Fib(a[k-1]) > 2 * Fib(a[k]).此時先手的策略為從n箇中取走Fib(a[k])個,這樣問題演化為後手面對 n’= Fib(a[1]) + Fib(a[2]) + … + Fib(a[k-1])且後手第一次不能取完Fib(a[k-1]). 由定理2,該問題可以演化為k-1個後手面對Fib(a[u]), u=1,2,…,k-1的“先手必勝”的子問題,即先手可以依次取完Fib(a[k-1]), Fib(a[k-2]), …, Fib(a[1]),從而先手會取走最後一個,先手必勝。
------------------------------------------------------------
程式碼
#include<cstdio>
bool is_fib(int n) // 判斷n是否是斐波那契數列中的元素(n>=2)
{
int i = 2, j = 3, k;
while (n >= i)
{
if (n == i || n == j)
{
return true;
}
else if (i < n && n < j)
{
return false;
}
else
{
k = j;
j += i;
i = k;
}
}
return false;
}
int main()
{
int n;
while (scanf("%d", &n))
{
if (n == 0)
{
break;
}
printf(is_fib(n) ? "Second win\n" : "First win\n");
}
return 0;
}