POJ 3922A Simple Stone Game

题目链接 A Sample Stone Game

  题目大意:给定n,k,表示最初时有n个石头,两个人玩取石子游戏,第一个人第一次可以取1~n-1个石头,后面每个人最多可以拿走前面一个人拿走的个数的K倍,当有一个人可以一次性全部拿走时获胜。问两人都在不失误的情况下,先拿着有没有必胜局势。有的话求他第一次最少该取多少个。

思考过程:

首先讨论k=1的情况,我们可以把一个数n(石子的个数),写为二进制下的表示,那先者取走最后一个1,那后者必然不能取走比它高一位的1,那么先拿者一定会赢,当然如果n本来就是2^i形式,那么先拿着当然不能拿走最后一个1,这样的话,石子就会取完了,所以这时先取者必输。举个例子来说 N = 20,他的二进制表示就是

1、10100              先拿者A拿走最后一个1(相当于拿走4个)n变为

2、10000              这时后取者B只能拿m=1,2,3,4个,假如是拿走3个,那么n变为

3、01101              这样A又可以取走最后一个1,。  

   ... ...                 这样继续下去,当这个数n变为1时

   00001,            最后一个1一定还是A拿走的,所以A必赢

而其实第2步中,不管B拿走多少个(即不管m是多少m = 1 or 2 or 3 or 4),在她之后的那个人又一定可以取走最后一个1(即取走x = (1<<y)),且这个数x一定小于等于m(定理1),这样的话B永远也不可能取完。

  而如果最初,n就等于2^j,二进制就是           10000...               那A自然不可能取走最后一个1(那就取了n个了),这样的话A取走一个m剩下的数就转化为上面,第1步中的形式了,那B一定就是必赢了

 

然后是k=2时定理2可以知道任何一个非菲波拉契数n都可以写为多个两两不相邻的斐波拉契数的和,这样的话先取者A先拿走一个小的斐波拉契数x,那后取者就无法取到另一个较大的菲波拉契数y(因为x与y不相邻,所以y>2*x)。这样又给A留下了一个非斐波拉契数,一直递归下去,A一定就是赢者。

  但同时,如果A最初面临的就是一个菲波拉契数Fn,但是Fn只能写为Fn-1 + Fn-2,如果取Fn-2,由于Fn-1 < 2 * Fn-2,所以B可以取完,如果取其他任意一个非斐波拉契数m,B得到的那剩下的数Fn-m就相当于上面的A最初面临的n,也就是说B必赢。

 

而对于一般的K,由上面的思路,我们的目的就是要让n写为一些数的和n1 + n2 + ...+np,而且它们两两相邻之间的倍数关系应该大于k,这样的话我们先者A取走小一点的数n1,后者B必然不能取走n2,就给A留下了一些数,这样的话B自然不能取完所以A获胜。

  在解题时,那我们就应该求出某个数列a[],这个数列要满足任何一个不再这个数列里面的数都可以用这个数列里的(满足前提条件前一项的k倍小于后一项的)多项的和表示(就好似定理2中任何一个数都可以写为多个个不连续的菲波拉契数(不连续就是相当于前一个的k=2倍小于后一个数)的和是同样的原理)。

  而为了要求出这个数组a,我们引入另一个数组b[],b[i]表示a[0],a[1]...a[i]可以找到两项a[x]和a[y]满足a[x] * k < a[y],且可以构造出的数(即a[x]+a[y])的最大值。

  假设a的前i项已经求出,由于前i项最大已经可以构造出b[i],那么b[i]+1就无法被构造出来,所以需要另外开一项a[i+1] = b[i]+1。这时我们要求b[i+1]由于b[i+1]表示的是前i+1个a可以构造出来的最大的数所以一定得用到a[i+1],同时又要满足必须存在一项a[x]使得a[x]*k<a[i+1],所以我们就从之前的a中找出这个最大的a[x],那么b[i+1]=a[x]+a[i+1]。但是有可能之前数据太少,找不到a[x] * k < a[i+1];那也就是相当于a[i+1]无法构造出来,因此b[i+1] = a[i+1](例如i=0,a[x]只能取a[0],而a[0]*k>=a[1],那么b[1]只能等于a[1])。 提炼出来就是:

if(a[x]*k<a[i+1])

  b[i+1] = a[x] + a[i+1];

else

  b[i+1] = a[i+1];

  然后就是要解决第一步取得最少且获胜的数量,那么先从n里减去小于n的最大的a[],然后又递归下去求这个剩下的n里最大的a[],找到n==a[]的a[]。也就是说这是这一串和里面最小的一个a了,将他输出就行了

 

 

定理1对任意一个不可以写为2^k(k>=1)的数n,从它的二进制表达式中先取走最后一个1(即拿走m_A = (1<<k1)),得到一个数n1,那么再从n1中取走m_B(m_B<=m_A)个,得到n2。那么n2的二进制中最后一位1(第k2位)所在的位置所表示的数一定小于等于m_B((1<<k2) <= m_B)。

证明:我们可以把m_B写为二进制形式,而它的二进制中所有的1所在的位置我们记为a1,a2,a3....ak(k>=1)(a1<a2<a3<...<ak),也就是说m_B = (1<<a1)+(1<<a2)+...+(1<<ak)。那我们用n1 - m_B得到n2,由于在n1中a1位置是0,a1以下的所有位置都是0,而m_B中a1位置是1,m_B在a1以下位置也都是0,所以相减后得到的n2在a1位置一定是1。用二进制表示就是:

n             100...100...100000...          拿走最后一个1(m_A = (1<<k1)),得到

n1            100...100...000000...          再随便找一个m_B<=m_A

m_B           000...000...010110...          加粗部分从左至右为ak,ak-1...a2,a1,然后用n1 - m_B得到

n2            100...011...101010...          n2在m_B最后一个1的位置(蓝色位置)也必定为1

 

定理2:任何一个非斐波拉契数都可以写为若干个不相邻的菲波拉契数的和,任何一个菲波拉契数都不能写为两个非相邻的斐波拉契数的和(原因很明显,因为Fn = Fn-1 + Fn-2,Fn-1与Fn-1相邻)

上面的思想过程转自神牛http://hi.baidu.com/lccycc_acm/item/a6f0dd0ec5c44a39f3eafcd3

有了上面的思想,代码实现就很简单了

 1 #include <stdio.h>
 2 
 3 int a[2000000],b[2000000];
 4 int main()
 5 {
 6     int n,k;
 7     int T = 0,Case = 1;
 8     while(~scanf("%d",&T))while(T--)
 9     {
10         scanf("%d%d",&n,&k);
11         a[0] = b[0] = 1;
12         int i=0,j=0;
13         while(n > a[i])
14         {
15             i++;
16             a[i] = b[i-1] + 1;
17             while(a[j+1] * k < a[i])
18             {
19                 j++;
20             }
21             if(k * a[j] < a[i])
22             {
23                 b[i] = b[j] + a[i];
24             }
25             else b[i] = a[i];
26         }
27         printf("Case %d: ",Case++);
28         if(n == a[i])printf("lose\n");
29         else
30         {
31             int ans ;
32             while(n)
33             {
34                 if(n >= a[i])
35                 {
36                     n -= a[i];
37                     ans = a[i];
38                 }
39                 i --;
40             }
41             printf("%d\n",ans);
42         }
43 
44     }
45     return 0;
46 }

 

 

 

 

转载于:https://www.cnblogs.com/gj-Acit/p/3219280.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值