题解 UVA1567 【A simple stone game】
题目大意
一堆石子有n个,首先第一个人开始可以去1~ 𝑛 − 1 个(就是不能取完),接下来两人轮流取石子。 每个人可取的石子数必须是一个不超过上一次被取的石子的𝑘倍的整数,先取完的人获胜,问先手是否必胜,必败输出lose,必胜输出第一步的操作。
有多组数据,每组给出n和k
首先考虑k=1的情况
先打个小点的表
n | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|
P/N | P | N | P | N | N | N | P | N |
可以发现所有败的状态都是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情况时的规律
n | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
P/N | P | P | N | P | N | N | P | N | N | N | N | P | N | N | N | N | N | N | N | P | N |
可以发现必败的状态为斐波那契数列上的数
因为正整数都可以用斐波那契数列上的数分解
把一个数用斐波那契数列从大到小分解
分解出的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; }