【NOIP2012模拟10.17】石子游戏

206 篇文章 0 订阅
2 篇文章 0 订阅

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<iostream>
#include<cstdio>
#include<cstring>
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]<n)		// 构建数列
        {
            a[i+1]=b[i]+1;
            while (a[t+1]*k<a[i+1]) t++;
            if (a[t]*k<a[i+1]) b[i+1]=b[t]+a[i+1]; else b[i+1]=b[i]+1;
            i++;
        }
        printf("Case %d: ",t1);
        if (a[i]==n) printf("lose\n"); else
        {
        	int ans;
            while (n)		// 构成 n 的最小的数列中的数字,即为第一次要取的数字
            {
            	if (n>=a[i])
                {
					ans=a[i];
					n-=a[i];
				}
				i--;
			}
			printf("%d\n",ans);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值