题目大意:
就是现在有一堆石子有n颗
现在先手起始的时候能取走任意颗(至少一个, 且先手第一次不能全部取完), 然后接下来轮流取石子的时候两个人取的数量不能超过对方上一次取的两倍
取走最后一颗石子的人赢, 问先手胜还是后手胜
大致思路:
这个题看了题解之后才知道是斐波那契博弈的模型
参考资料:点击打开链接
Fibonacci Nim模型:
现在有一堆n石子, 两个人轮流取石子, 要求满足
1. 先手第一次不能将所有石子一次取完
2. 接下来每次取的石子个数不能超过上一次的两倍, 且至少为1
3. 取走最后石子的人胜
结论: 当且仅当石子的个数为Fibonacci数的时候, 后手胜, 即该点时P点, 必败点
证明如下:
首先了解到Fibonacci的递推式 fib[i] = fib[i - 1] + fib[i - 2], 不妨设fib[0] = fib[1] = 1
对于n = 1 or 2的情况, 很明显先手胜
现在用第二数学归纳法给出证明, 所有的n = fib[i]的点都是必败状态
假设fib[i - 1], fib[i - 2]是必败状态
那么fib[i] = fib[i - 1] + fib[i - 2]
由于不等式fib[i - 1] <= 2*fib[i - 2], 当先手取x个石子且 x >= fib[i - 2]时, 后手可以将剩下的一次取完, 后手胜
当先手取 x < fib[i - 2]时, 根据之前的假设, 后手将会拿到fib[i - 2]这一部分中的最后一颗石子, 轮到先手先拿fib[i - 1]中的石子
根据假设, 如果先手不能一次拿完fib[i - 1]这一对的话依旧是后手拿到这一堆最后一个
下面考虑先手能拿的最多的数量, 当先手那个x = fib[i - 2] / 3时, 后手拿fib[i - 2]*2/3个, 此时先手对于fib[i - 1]起步最多, 为fib[i - 2]*4/3
下面比较fib[i - 2]*4/3与fib[i - 1]的大小
由于fib[i - 2]*4 - 3*fib[i - 1] = fib[i - 2] - 3*fib[i - 3] 而 fib[i - 2] <= 2*fib[i - 3]所以先手不可能一次取完fib[i - 1]
那么先手必败, fib[i]是P点, 必败点
接下来证明所有的不是Fibonacci数的点都是必胜点, N点
这里需要借助Zeckendorf定理 (齐肯多夫定理) : 任何一个正整数都可以表示成若干个不相邻的Fibonacci数的和
齐肯多夫定理的证明可以参见这里:齐肯多夫定理证明
那么对于一个n被分成若干个( >= 2个)Fibonacci数的和
n = fib[a1] + fib[a2] + ... + fib[ak] 其中 a1 < a2 < a3 < ... < ak 且 ai != a(i + 1) + 1
那么就有fib[ai]*2 < fib[a(i + 1)]
于是如果先手取完fib[a1]然后后手不可能一次取完剩下的任何一部分fib堆, 于是就变成了后手先手的局面了, 这样子先手一定会获得胜利
于是这题就很好做了, 普通的Fibonacci博弈模型
代码如下:
Result : Accepted Memory : 1612 KB Time : 0 ms
/* * Author: Gatevin * Created Time: 2015/4/29 9:54:56 * File Name: Rin_Tohsaka.cpp */ #include<iostream> #include<sstream> #include<fstream> #include<vector> #include<list> #include<deque> #include<queue> #include<stack> #include<map> #include<set> #include<bitset> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<cctype> #include<cmath> #include<ctime> #include<iomanip> using namespace std; const double eps(1e-8); typedef long long lint; #define foreach(e, x) for(__typeof(x.begin()) e = x.begin(); e != x.end(); ++e) #define SHOW_MEMORY(x) cout<<sizeof(x)/(1024*1024.)<<"MB"<<endl int n; lint fib[100]; int main() { fib[0] = fib[1] = 1; for(int i = 2; fib[i - 1] <= (1LL << 31); i++) fib[i] = fib[i - 1] + fib[i - 2]; while(scanf("%d", &n), n) { for(int i = 2; fib[i] <= n; i++) if(fib[i] == n) { puts("Second win"); goto nex; } puts("First win"); nex:; } return 0; }