由K倍动态减法联想到的一类博弈问题 HDOJ 2580

给一堆石子,数量为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;
}

点赞