Poj-3922 A simple stone game(k倍动态减法)

题意:

游戏是这样的:两个玩家以一堆n个石头开始游戏。他们轮流从石堆里取石头,每次至少取一块。先走的人第一步最多可以拿n-1块石头。从那时起,一个玩家最多可以拿k倍于他的对手上次拿的石头。例如,如果一个玩家轮流拿m块石头,那么另一个玩家下一次最多可以拿k×m块石头。谁拿了最后一块石头,谁就赢了这场比赛。

 

题解:

原文:k倍动态减法

斐波那契博弈是本题当k==2的一种特殊情况:

 

当k==1的时候:

当n=2^i (i^2 把i和2按位异或) 的多少次方的时候,那么这就是一个必败态,因为此时k==1,那么你把它化为二进制,这个时候拿掉二进制的最后一个1,那么对方必然不能拿走倒数第二位的1,因为他不能拿的比你多。你只要按照这个策略对方一直都不可能拿完。所以你就会赢。

而当分解的二进制中只有一个1时,因为第一次先手不能全部取完,所以后手一定有办法取到最后一个1,所以必败!
          举个例子,当 n = 6 = (110)时:
                 第一轮:先手第一次取最右边的1,即2个,此时还剩4(100)个,后手能取1或2个;
                 第二轮:假如上轮后手取的两个,先手再取两个直接赢了
                               假如后手取了一个,那么还剩三个,自己只能去1个,以后也只能取一个,所以必胜! 

 

当 k = 2 时,赤裸裸的Fibonacci博弈了,具体这儿不多说,自己再上述博客已写的很明白了。其实n经拆解后也可以表示成二进制的形式,用 k = 1时的方法来理解,比如 n = 11 = 7 + 3 + 1,可表示成 10101;


 当 k 取任意非零正值时,重点来了:
          犹如Fibonacci博弈,我们首先要求一个数列,将n分解成数列中一些项的和,然后就可以按Fibonacci博弈的解决方法来完成,也可以按二进制的方法来理解,每次取掉最后一个1 还是符合上面的条件。
          我们用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=数列中的数,则必败,否则必胜;必胜时还要求输出第一步取法,其实就是分解的数列中最小的一个

 

代码:

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

 

posted @ 2019-08-17 17:47  kongbursi  阅读(222)  评论(0编辑  收藏  举报