Description
两人取一堆n个石子 先手不能全部取完 之后每人取的个数不能超过另一个人上轮取的数*K。取完最后一个石子的人获胜。给n,K判断先手必胜并求第一步。
Input
输入文件名为 stone . in。
第一行为一个正整数t(1<=t<=10),表示共t组测试数据
接下来t行,每行包括两个正整数n,k
Output
输出文件名为stone.out。
共t行,第i行先输出“Case i: ”(不包括引号),接着输出结果,若先手有必胜策略则输出第一次取的石子数(答案不唯一,输出第一步最小选几),否则输出lose。
Sample Input
5
16 1
11 1
32 2
34 2
19 3
Sample Output
Case 1: lose
Case 2: 1
Case 3: 3
Case 4: lose
Case 5: 4
Data Constraint
Hint
对于10%的数据k=1;
对于30%的数据1<=k<=2;
对于100%的数据2<=n<=100000000,1<=k<=100000。
.
.
.
.
.
分析
当k=1的时候 可知必败局面都是2^i 将n分解成二进制,然后先手取掉最后一个1.然后对方必然无法去掉更高的1,而对方取完我方至少还能拿掉最后一个1 导致对方永远取不完。
当k=2的时候,必败局面都是斐波那契数列。利用“先手去掉最后一个1,则后手必不能去掉更高阶的1导致取不完”的思想,斐波那契数列有一个非常好的性质就是:任意一个整数可以写成斐波那契数列中的不相邻的项的和,于是将n写成这种形式,先取走最后一个1,对方能取的数是这个数*2,小于高2位的1,所以取不完。
当k>=3的时候, 想办法构造数列,将n写成数列中一些项的和,使得这些被取到的项的相邻两个倍数差距>k 那么每次去掉最后一个1 还是符合上面的条件。设这个数列已经被构造了i 项,第 i 项为a[ i ],前 i 项可以完美对1…b[ i ] 编码使得每个编码的任意两项倍数>K 那么有
a[ i+1 ] = b[ i ] + 1;这是显然的 因为b[ i ] + 1没法构造出来,只能新建一项表示
然后计算b[ i+1] 既然要使用 a[ i+1 ] 那么下一项最多只能是某个 a[ t ] 使得 a[ t ] * K < a[ i+1 ] 于是
b[ i ] = b[ t ] + a[ i+1 ]
然后判断n是否在这个数列里面
如果在,那么先手必败。否则不停的减掉数列a中的项构造出n的分解,最后一位就是了。
——————————————————————————————————————————————
k<=2的情况都好理解,我们在理解k<=2的基础上分析k>=3时,如何理解构建a、b的问题:
犹如Fibonacci博弈,我们首先要求一个数列,将n分解成数列中一些项的和,然后就可以按Fibonacci博弈的解决方法来完成,也可以按二进制的方法来理解,每次取掉最后一个1 还是符合上面的条件。
i为当前已构建数列中的第i项
我们用a数组表示要被求的数列,b数组中的b[i]保存 a[0…i] 组合能够构造的最大数字。
这儿有点难理解,所谓构造就是指n分解为Fib数相加的逆过程。
举例说明,当k = 2 时,a[N]={1, 2, 3, 5, 8, 13, 21, 33…} (Fibonacci数组);那么b[3] 即 1、2、 3 能够构造的最大数字,答案是4,有点匪夷所思?或许你会问为什么不是5、6或者其它的什么,其实是这样的 ,4 能分解成 1+3 是没有争议的,但5能分解成2+3吗? 不能,因为5本身也是Fibonacci数;6虽然能分解,但不是分解成1+2+3,而是分解成1+5。
经过上述,我们知道b[i] 是 a[0…i] 能够构造出的最大数字,那么a[i +1] = b[i]+1;
因为a数组(Fib数组)所存的数字都是不可构造的(指取到它本身就是必败态),显然a[0…i]构造的最大数字 + 1 即为下一个不可构造的数字了(a[i + 1])。
然后关于b[i]的计算,既然是a[0…i]构造最大数字,那么 a[i]是一定要选用的(这儿需要一定的推理,a[i]构造数字时,相邻的j个是不能同时用的,就像上述的2、3不能构造出5一样,推理请自己完成),那么要选用的下一项只能递减寻找,直到找到 最大的a[t] 满足 a[t] * k < a[i] ,而b[t]就是a[0…t]所能构造的最大数字,再加上a[i], 即为a[0…i]能构造的最大数字,于是b[i] = b[t] + a[i]。
求的数列后,之后的工作就简单了,跟Fibonacci博弈一样一样的,如果n=数列中的数,则必败,否则必胜;必胜时还要求输出第一步取法,其实就是分解的数列中最小的一个,将见代码。
.
.
.
.
.
程序:
#include
#include
#include
using namespace std;
int a[2000010],b[2000010],t,n,k; // a 为数列, b 保存 a[0...i] 能构造出的最大的数
int main()
{
scanf("%d",&t);
for (int t1=1;t1<=t;t1++)
{
scanf("%d%d",&n,&k);
int i=0,t=0;
a[0]=b[0]=1;
while (a[i]=a[i])
{
ans=a[i];
n-=a[i];
}
i--;
}
printf("%d\n",ans);
}
}
return 0;
}