题解 UVA1567 【A simple stone game】

题目大意

一堆石子有n个,首先第一个人开始可以去1~ ? − 1 个(就是不能取完),接下来两人轮流取石子。 每个人可取的石子数必须是一个不超过上一次被取的石子的?倍的整数,先取完的人获胜,问先手是否必胜,必败输出lose,必胜输出第一步的操作。

有多组数据,每组给出n和k 


首先考虑k=1的情况

先打个小点的表

n23456789
P/NPNPNNNPN

可以发现所有败的状态都是2的i次方

为什么呢

考虑把n进行2进制分解

当n不为2的i次方

例n=22分解后为10110

先手可以去掉最后面的一个1

由于后手取的数只能小于前个取的数的k,而k为1

这样的话后手一定不能去掉一个更高位的1,只能在后面的0中取

后手取完之后后面一定会生成至少一个新的1

前面的数值就会减少

先手就可以再通过上面的方法取最后一个1

这样到最后最后一个1就是先手的,所以必胜

而当n为2的i次方

由于题目限制不能一次性取完 所以在先手第一次取完之后

后手再按上面一种情况的方法就可必胜,先手就必败


再考虑k=2的情况

再打个表(手推)和分析k=1情况时的规律

n2345678910111213141516171819202122
P/NPPNPNNPNNNNPNNNNNNNPN

可以发现必败的状态为斐波那契数列上的数

因为正整数都可以用斐波那契数列上的数分解

把一个数用斐波那契数列从大到小分解

分解出的1一定不是相邻的

否则通过斐波那契数列的性质相邻的1一定可以合成更大的一个数

而在斐波那数列中 中间位置隔至少为1的两个数

大的数一定为小的数的两倍以上 所以也无法取到更大的一个1

也像k=1的情况那样 只能在后面的0中取

按照k=1的方法就可知道必胜和必败态


K为其他的情况

有了上面的规律就可以扩展到k为其它数的情况

我们需要想办法构造出适用于k的类似上面两种情况的数列

条件:

1.使小于等于n的数都能被该数列上的数分解
2.一个数的 用于分解的 从大到小的 数列上的数之间 相邻数的倍数大于k倍

这样就可以用k=1的方法解决了

那么该怎么构造呢

设我们需要构造的数列为a[ i ],当数列为长度为i时最大能合成的数为b[ i ]

我们先假设我们已经构造了i项这个数列

因为b[ i ]是此时能合成的最大的数

那么b[ i ]+1的数此时就不能合成 理所当然的a[ i+1]就需要等于b[ i ]+1

求现在最大能构造的数b[ i+1] 显然也需要用到a[ i+1]

因为需要满足分解的相邻两项的倍数大于k

设 最后一个与a[ i+1]倍数大于k的位置为j

所以b[ i+1]=b[ j ]+a[ i+1];

如果没有倍数大于k的数那么显然b[ i+1]=a[ i+1];

而在b[ i ]到b[ i+1 ]之间的数由于1到b[ j ]的数都能合成和算上a[ i+1]

所以也都能合成出来

显然因为1也需要合成

所以初始状态 a[ 0 ]=1所以直接得出b[ 0 ]=1

递推下来这样序列就可以构好了

整理一下递推的式子(i为当前要求的 j为倍数大于k的最后个位置)

1.初始状态:--a[ 0 ]=b[ 0 ]=1;

2.a的递推:---a[ i ]=b[ i-1]+1;

3.b的递推:---if(a[ j ]*k<a[ i ]) b[ i ]=b[ j ]+a[ i ];

4.-------------else b[ i ]=a[ i ];

最后在考虑怎么得出答案

1.如果n为a中的数为必败直接输出“lose”

2.否则为必胜 从大到小枚举a[ i ],n>=a[ i ]减去 记录最后一个被减的i的位置

还有些细节还是看代码吧


代码

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define C getchar()-48
inline ll read()
{
    ll s=0,r=1;
    char c=C;
    for(;c<0||c>9;c=C) if(c==-3) r=-1;
    for(;c>=0&&c<=9;c=C) s=(s<<3)+(s<<1)+c;
    return s*r;
}
const int N=20000010;
int n,k,casen=1;//casen 记录当前是第几组数据 
int a[N],b[N]; 
int main()
{
    int t=read();
    while(t--)
    {
        n=read(),k=read();
        a[0]=b[0]=1;//初始状态 
        int i=0,j=0; 
        while(n>a[i])//递推求a,b   因为还要判断n是否在数列上所以递推a到大于n为止 
        {
            i++;
            a[i]=b[i-1]+1;//推a 
            while(a[j+1]*k<a[i]) j++;//查找倍数大于k的最后一个位置 
            if(a[j]*k<a[i]) b[i]=b[j]+a[i];//推b  
            else b[i]=a[i];
        }
        printf("Case %d: ",casen++);
        int ans;
        if(n==a[i]){printf("lose\n");continue;}//必败 
        for(;n;i--)//必胜 找最后一个被减的i 
        if(n>=a[i])
        {
            n-=a[i];
            ans=a[i];
        }
        printf("%d\n",ans);
    } 
    return 0;
}

转载于:https://www.cnblogs.com/1436177712qqcom/p/10375786.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值