算法作业——ACM斐波那契博弈

总结算法汇报作业——取石子游戏


  •  算法题目

 取石子游戏

1堆石子有n个,两人轮流取.先取者第1次可以取任意多个,但不能全部取完.以后每次取的石子数不能超过上次取子数的2倍。取完者胜.先取者负输出"Second win".先取者胜输出"First win".

题目来自杭电ACM题库:http://acm.hdu.edu.cn/showproblem.php?pid=2516

此题目的算法大背景是博弈论,这道题目是斐波那契博弈模型,之所以称作斐波那契博弈是因为这个模型的必败态是斐波那契数,也就是说只要石子总数是斐波那契数,那先手必输。 

  • 斐波那契博弈

  • 结论  

 石子数为斐波那契数时,先手必败 

  • 分析

 n = 2时,输出second;

 n = 3时,无论先手取1个还是2个石子,后手都能一下取完,输出second;

 n = 4时,第一个人想获胜就必须先拿1个石子,这时就变成了n为3的情形,输出first;

 n = 5时,first不可能获胜,因为他取2时,second直接取掉剩下的3个就会获胜,当他取1时,这样就变成了n为4的情形,输出second;  

以此类推,可以发现石子数为斐波那契数时,先手必败。 

  • 证明

首先需要了解Fibonacci的性质:(2, 3, 5, 8, 13, 21, 34, 55··· ···)
       1.f(n) = f(n-2) + f(n-1)
       2.2f(n) > f(n+1)
       3.4f(n) < 3f(n+1)

1、石子总数n是斐波那契数时:(数学归纳法证明)

  • 当n=2时,先手必败。
  • 假设当n<=f(k)时,结论成立。
  • 则只需证明n=f(k+1)时结论成立即可。 

f(k+1)=f(k)+f(k-1),其中f(k)>f(k-1),我们假设先手取得石子数为x,后手取得石子数为y,下面分两种情况讨论:

(1)x>=f(k-1),由斐波那契的性质2可知,2f(k-1)>f(k),所以剩余的石子,后手可以一下取完,先手败。

(2)x<f(k-1),把石子分成f(k)和f(k-1)两堆,其中f(k-1)这一堆,由假设可知,必然是后手先取完,那先手再取f(k)这一堆时,能不能一下子取完就和后手最后取完f(k-1)这一堆石子的个数有关,如果能一下子取完f(k),则先手就取得胜利,如果一把取不完,那就由假设可知,先手败。故下面讨论后手最后取完f(k-1)时,取的石子个数y的情况:

当x>=f(k-1)/3,则y<=2f(k-1)/3 。

当x<f(k-1)/3,不能一轮就把石子数取完,但是石子数会越越少,所以最终石子数y还是<=2f(k-1)/3。

由斐波那契数列的性质3可以知道, 4f(k-1)<3f(k),所以2y<=4f(k-1)/3<f(k),所以我们可以得知最后一堆石子f(k),先手不能一下子取完,故先手败。

由此得证。

2、石子总数不是斐波那契数:

此时需要借助齐肯多夫定理任何正整数都可以表示成若干个不连续的斐波那契数之和。

例如:85=55+30=55+21+9=55+21+8+1。

令:n = f(an) + f(an-1) + ··· ··· + f(a3) + f(a2) + f(a1),其中an>an-1>.......>a2>a1。
       先手首先拿走最小的f(a1)这一堆。因为不是连续的Fibonacci数, 即a2 > a1 + 1。所以f(a2)肯定大于2f(a1),即后手不可能一次把f(a2)堆拿完,则对于f(a2)这堆后手必输,同理,其他堆一样,故先手赢。

  • 代码 
#include <iostream>
using namespace std;
const int maxn = 100 + 5;
long long f[maxn];
int n;
void getFibonacci() {
    f[0] = 2;
    f[1] = 3;
    for(int i = 2; i <= maxn; i++) {
        f[i] = f[i-2] + f[i-1];
        if(f[i] > 2147483648) 
			return;
    }
}
int main(int argc, char** argv) {
	getFibonacci();  
	    
    while(cout<<"\n请输入石子总数:\n",cin >> n, n) {
        int flag = 0;
        for(int i = 0; i < maxn; i++) {
            if(f[i] == n) break;
            else if(f[i] > n) 
				flag = 1;
        }
        if(flag) 
			cout << "First win" << endl;
        else 
			cout << "Second win" << endl;
    }

    return 0;
}

 借助二叉树分析可得时间复杂度:O(2^n)。

 

 

 

猜你喜欢

转载自blog.csdn.net/Exception_3212536934/article/details/110739085