给一堆石子,数量为n,先取的人最多能取n – 1个,之后每次最多能取上次对手取的石子的数量的k倍
当k = 1时必败数列为(1, 2, 4, 8 ……),当n的二进制中不止一位为1时,我们每次取n的二进制的最低一非0位数量,那么对手就绝对不能取完(因为高位的是地位的至少二倍),而对手不管取x为多少,假设x的最高位1为w,那么取后剩余石子n的w位或者w以下一定有1,那么我们就又可以取最低一位1了,依次进行,只有自己才能胜利。相反n的二进制只有一位,而我们又不能取完,相当于自己不能取最后一位非0位,对手就能胜。
当k = 2时必败数列为(斐波那契数列)(1,2, 3, 5, 8……),每个n都可以用数列中的不相邻数字相加得到,假设有至少两个数字(从小到大排序)(x1, x2……)相加,我们每次取最小的x1,因为x1,x2不相邻,及x1,x2至少相隔1个数字,观察斐波那契数列:隔1数字的两个数字大的是小的的至少两倍。那么对手绝对不能取完x2,对手取完后又得到一数字n1,依次进行,最后只有可能自己拿完石子。如果n及为数列中的数字,那么该n只能由一个x1相加组成,这时候我们不能取完,及不能取走最小的数字,相当于对手的胜局。
当k = n时,我们就要构造自己的数列
首先失败点为数列array(1),最大相加能组成的为max(1)
因为前面的array【i】最多能组成max【i】,max【i 】 + 1没法由数列中的数字相加组成
我们可以新建一个点,array【i + 1】 = max 【i】+ 1,解决问题;
新加了一个节点后最大能组成的数字应该为max【j】 + array【i + 1】;
因为要满足组成数字n的所有数字(x1, x2, x3……)相差至少k倍,所以array【j】满足array【j】 * k < array【i + 1】;
我们求的是最大能表示的数字,这时候只需要j足够大就可以了
很明显,j是随着失败数列中的数字的增多而增大的,由此优化算法,避免超时
HDOJ 2580 我的代码
#include<stdio.h>
__int64 max[3000001], array[3000001];
int main()
{
__int64 n, k, i, j;
int cases, ca = 0;
scanf("%d", &cases);
while(cases --)
{
scanf("%I64d%I64d", &n, &k);
array[0] = 0;
max[1] = 1;
array[1] = 1;
i = 1;
j = 1;
while(array[i] < n)
{
i ++;
array[i] = max[i - 1] + 1;
while(array[j] * k < array[i])
j ++;
j --;
max[i] = max[j] + array[i];
if(i == 100000)
printf("");
}
if(array[i] == n)
printf("Case %d: lose\n", ++ca);
else
{
while(1)
{
if(n == array[i])
{
printf("Case %d: %I64d\n", ++ca, array[i]);
break;
}
if(n > array[i])
{
n -= array[i];
}
else
{
i --;
}
}
}
}
return 0;
}