原题链接
题面
有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。如果你胜,你第1次怎样取子?
Input
输入包含若干行,表示若干种石子的初始情况,其中每一行包含两个非负整数a和b,表示两堆石子的数目,a和b都不大于1,000,000,且a<=b。a=b=0退出。
Output
输出也有若干行,如果最后你是败者,则为0,反之,输出1,并输出使你胜的你第1次取石子后剩下的两堆石子的数量x,y,x<=y。如果在任意的一堆中取走石子能胜同时在两堆中同时取走相同数量的石子也能胜,先输出取走相同数量的石子的情况.
Sample Input
1 2
5 8
4 7
2 2
0 0
Sample Output
0
1
4 7
3 5
0
1
0 0
1 2
题目大意
这道题就是典型的威佐夫博弈(Wythoff Game)。只不过这一道题需要一些更进一步的思考。这道题需要求出第一次取石子的方法,然后输出第一次去完之后剩下的两堆石子的数量分别是多少。也是对威佐夫博弈问题更深的理解。
解题思路
我们知道威佐夫博弈问题中有两个公式,这是解决这类问题的关键。
我们假设这两堆石子的数量分别是 a 和 b ( a < =b)。
那么 我们有这两个公式 :
k=b-a
temp= ( 1+sqrt (5) ) / 2 * k (sqrt ( 5 )= √5 也就是根号5 ,最后 temp 需要取整) ( 1+sqrt (5) ) / 2 ≈ 1.618
其中,当 temp=a 时,此状态为必败态。也就是说谁面临这个状态,谁就必输。
现在有了这两个公式我们就可以解这一道题了。
对于两堆石子每堆取相同的情况:
因为 temp 的值 与 k 有关。并且 k 就是 b 和 a 的差值。 并且因为是两堆取相同数量的石子,所以 k 值不变。但是 a 和 b 的值是变化的。 比如说 a=1 b=3 和a=1000 b=1002 的k值都是2 。又因为当 temp = a 时为必败态。那么先手就可以先取一些石子,让对手面临必败态。所以我们可以直接每一堆里面都拿(a-temp)个石子。
对于从一堆石子里面拿任意数量石子的情况:
对于这一种情况,我们有两种取法:
(1) 如果从 a 堆中取,a就会变小,又因为 k=b-a,所以 k 的值就会变大 ,又因为temp ≈ 1.618 * k ,所以 temp 也会变大。而为了达到必败态 就必须使 temp = a ,a在减小,temp再变大,显然非常矛盾。所以此情况不成立。
(2)如果从 b 堆中取,b会变小,k变大,temp变小。那么只要b能小到一定程度那么temp就可以等于b。那么是不是这样呢?我们再来分析一下,当b逐渐变小,最后越来越接近a,在最后等于 a ,此时 k=0,temp =0,而 b =a 肯定不等于0 。
估计很多同学看到这里就是一脸懵。怎么会怎么取都不对呢?
别着急,继续往下看。
那么有没有可能说 从 b 里面取,最后使 b 小于 a 呢?那么接下来我们继续分析。当 b < a 的时候,此时的大小关系也发生了变化,temp 也发生了变化。假如说此时的 b 变成了 t ( t< a),那么我们就会有
k=a-t
temp=( 1+sqrt(5) ) / 2*k
那么这时,有些同学估计更懵了。这不是和情况(1)一样吗,这道题这样分析不就无解了?
别慌,听我说。
此时,我们的temp是从 0 开始变化的,并且此时的 a 与 b 的大小关系也发生了变化。原来较大的 b 变成了较小的 t ,较小的 a 变成了较大的 a ,因为这个转变使得下面的分析成立。因为我们的 t 就是从 a 开始的。也就是说如果 t 从 a 开始减小 ,temp 从0 开始增大,那么就有可能使 t =temp ,从而达到必败态。
是不是有一种眼前一亮的感觉(手动滑稽),想不明白的同学多看几遍,肯定会眼前一亮的。那么这时,我们可以解一下方程就可以得到一个方程式。
字写得不好,能看懂就行。
现在我们可以写这道题了。
AC代码
#include<iostream>
#include<stdio.h>
#include<stack>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<queue>
#include<string>
#define inf 0xffffff
using namespace std;
int main()
{
long long a,b,m,n,t;
while(~scanf("%lld%lld",&a,&b))
{
if(a==0&&b==0)
break;
if(a>b)
swap(a,b); //如果a比b大,交换a,b
int k=b-a;
m=(1+sqrt(5.0))/2*k;
if(m==a)
printf("0\n");
else
{
printf("1\n");
printf("%lld %lld\n",m,b-a+m); //先输出两堆都取的情况
n=(sqrt(5.0)-1)/2*a; //因为(sqrt(5.0)-1)/2≈0.618 所以n肯定小于a,不用考虑排序问题。
printf("%lld %lld\n",n,a);
}
}
return 0;
}
最后总结
本题别看思路那么多那么杂,其实做题的时候实际思考的时间并不长。重点培养思维方式,对做任何题都有帮助。