a simple stone game--k倍动态规划减法游戏

a simple stone game

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 298    Accepted Submission(s): 202


Problem Description
After he has learned how to play Nim game,Mike begins to try another stone game which seems much easier.

The game goes like this:Two players start the game with a pile of n stones.They take stones from the pile in turn and every time they take at least one stone.The one who goes first can take at most n-1 stones for his first move .from then on a player can take at most k times as many stones as his opponent has taken last time.For example,if one player take m stones in his turn,then the other player can taken at most k * m stones next time.The player who takes the last stone wins the game.suppose that those two players always take the best moves and never make mistakes,your job is to find out who will definitely win the game.
 

Input
The first line contains a integer t,indicating that there are t test cases following. (t<=20).Each test is a line consisting of two integer n and k.(2<=n<=10^8,1<=k<=10^5).
 

Output
For each test case,output one line starting with“Case N:”,N is the case number. And then,if the first player can ensure a winning ,print the minimum number of stones he should take in his first turn.Otherwise ,print “lose ”.Please note that there is a blank following the colon.
 

Sample Input
  
  
6 16 1 11 1 32 2 34 2 19 3 100000000 100000
 

Sample Output
  
  
Case 1: lose Case 2: 1 Case 3: 3 Case 4:lose Case 5: 4 Case 6: 912
此题是博弈问题,虽说作为一个博弈小白,但我还是想奉献上自己对此题的见解,大神们不要喷我QAQ--
首先,强烈建议去学习斐波那契博弈,这是“前置技能”!!!!!!!!!!!!!!!!!!!!!!!!
此题是博弈中的k被动态减法游戏,是斐波那契博弈的一个扩展,只是k可能不为2而已,建议先去搞懂斐波那契博弈,其他的换汤不换药。
设两个数组,a[],b[],a[]数组用来记录“斐波那契数列”,b[]数组用来找出a[]数组中最大的值,所以 a[i]=b[i-1]+1;b[i-1]是由a[0]......a[i-1]组成的最大的数,由此可见b[i-1]+1就不可能由a[]数列组成。
现在只需要if(a[j]*k<a[i])
b[i]=b[j]+a[i];
else (仔细思考一下这个else)
b[i]=a[i]

要求b[j],表示a[0]……a[i]组成,那么显然是要用到a[i]的,不然不就成了b[i-1],既然用了a[i],但是又要使相邻的倍数在K以上。则找到最大的j,使a[j]*k<a[i]那么满足条件,便是a[0]……a[j]能组成的最大的数,加上a[i],那么后者表示当前项不能和之前项组合,那么最大的数就只能是本身

附上我当时搜的大神的资料,看到这个我才搞懂的k倍动态规划的原理

思路:

博弈论中的 K倍动态减法游戏,难度较大,参看了好多资料才懵懂!

此题可以看作 Fibonacci 博弈的扩展,建议没弄懂 Fibonacci博弈的先学那个,个人整理 http://blog.csdn.net/tbl_123/article/details/24033245 ;

而说扩展体现在数列不再是Fib数列,是根据 k 的值自行构造的,其它换汤不换药,具体构造方法如下:

这儿方便说明白,首先根据k的值分情况讨论:

1) 当 k = 1 时,必败态为 n = 2 ^ i, 因为我们把数按二进制分解后,拿掉二进制的最后一个1,那么对方必然不能拿走倒数第二位的1,因为他不能拿的比你多。你只要按照这个策略对方一直都不可能拿完。所以你就会赢。而当分解的二进制中只有一个1时,因为第一次先手不能全部取完,所以后手一定有办法取到最后一个1,所以必败!

举个例子,当 n = 6 = (110)时:

第一轮:先手第一次取最右边的1,即2个,此时还剩4(100)个,后手能取1或2个;

第二轮:假如上轮后手取的两个,先手再取两个直接赢了

假如后手取了一个,那么还剩三个,自己只能去1个,以后也只能取一个,所以必胜!

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

3) 当 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=数列中的数,则必败,否则必胜;必胜时还要求输出第一步取法,其实就是分解的数列中最小的一个,将见代码。

    
    
大神的思路就是明白,再次膜拜,其实懂了斐波那契博弈后完全就会理解k倍动态规划的原理。
附上大神代码一份:
#include 
#define N 20000005

int a[N], b[N];			// a 为数列, b 保存 a[0...i] 能构造出的最大的数

int main()
{
    int n, k;
    int loop = 0, casei = 1;
	scanf("%d",&loop);
	while(loop --){
        scanf("%d%d",&n,&k);
        a[0] = b[0] = 1;
        int i = 0, j = 0;

        while(n > a[i]){			// 构建数列
            i ++;
            a[i] = b[i - 1] + 1;
            while(a[j + 1] * k < a[i])
                j ++;
            if(k * a[j] < a[i])
                b[i] = b[j] + a[i];
            else
				b[i] = a[i];
        }

        printf("Case %d: ", casei ++);
        if(n == a[i])
			printf("lose\n");
        else{
            int ans;
            while(n){
                if(n >= a[i]){		// 构成 n 的最小的数列中的数字,即为第一次要取的数字
                    n -= a[i];
                    ans = a[i];
                }
                i --;
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值