微软的扔杯子问题-动态规划

题目描述
一种杯子,若在第N层被摔破,则在任何比N高的楼层均会破,若在第M层不破,则在任何比M低的楼层均不会破,给你两个这样的杯子,让你在100层高的楼层中测试,要求用最少的测试次数找出恰巧会使杯子破碎的楼层。
不等式解法
所有讨论基于2个杯子的情况:
下图中所有图示的红线均表示剩下两个杯子的时候的试摔位置,当杯子破碎后,也就是只剩下一个杯子的时候,只需要从已知范围的最底层向已知范围的最高层逐层试摔。
当只有一层的时候,需要一次试摔。当有两层的时候,需要两次试摔。
当需要三层时候,首先讨论是否可以通过两次试摔完成,可以发现可以,方法如图。
当需要四层时候,首先讨论是否可以通过两次试摔完成,可以发现已经不行,因此至少需要三次试摔。
当需要五层时候,首先讨论是否可以通过小于等于三次试摔完成,如果不行则讨论最多四次试摔的情况。依次类推,如下图:


因此,当有100层的时候,最多需要n次试摔,使得 n+(n-1)+(n-2)+...+1 >= 100,然后对n上取整,因此n=14。最差情况需要14次试摔。试摔方法为:第一次在14层扔,如果没破,在27层(14+13)继续扔,不破在39层(27+12)继续扔,依次类推。 如果破了,剩下一个杯子,从已知范围的最底层向已知范围的最高层逐层试摔。
动态规划解法
假设用dp[i][j]表示剩余i层,j个杯子时候最少需要多少次试摔。
(1)dp[i][1] = i,也就是说当只剩下一个杯子时,只能从第一层开始逐层向上试摔。
(2)dp[0][j] = 0,当只剩下0层时,只需要进行0次试摔。
(3)dp[1][j] = 1,当只剩下1层时,只需要进行1次试摔,前提是j>0
(4)dp[i][j] = min(1=<k<j)(max(dp[k-1][i-1], dp[j-k][i]) + 1),也就是当杯子破和不破的时候分成两个子问题,分别求解,之后求子问题试摔次数的最大和作为这种分解方法的试摔次数,之后取这些所有分拆子问题的方法中,使子问题和最小的方法。
route[i][j],表示剩下i层j个杯子时,下一次试摔的楼层。当j=1时,从这个范围内的最底层依次向最高层试摔。
  1. //dp.c  
  2. #include <stdio.h>  
  3. #define LAYER 101  
  4. #define CUP 3  
  5. int main(){  
  6.     int dp[LAYER][CUP], route[LAYER][CUP], i, j, k;  
  7.     for(i=1; i<LAYER; i++){  
  8.         dp[i][1] = i;   
  9.         dp[i][0] = 0;  
  10.     }  
  11.     for(i=1; i<CUP; i++){   
  12.         dp[1][i] = 1;  
  13.         route[1][i] = 0;  
  14.         dp[0][i] = 0;  
  15.     }   
  16.     for(i=2; i<CUP; i++)  
  17.         for(j=2; j<LAYER; j++){  
  18.             dp[j][i] = j;  
  19.             route[j][i] = 0;  
  20.             for(k=1; k<j; k++){  
  21.                 int min = dp[k-1][i-1] > dp[j-k][i]? dp[k-1][i-1]+1: dp[j-k][i]+1;  
  22.                 route[j][i] = min <= dp[j][i]? k: route[j][i];  
  23.                 dp[j][i] = min < dp[j][i]? min: dp[j][i];  
  24.             }  
  25.         }  
  26.     printf("MIN STEP IS %d\n", dp[LAYER-1][CUP-1]);  
  27.     //for(i=1; i<LAYER; i++){for(j=2; j<CUP; j++){printf("%d\t", route[i][j]);} printf("\n");}  
  28.     return 0;  
  29. }  

输出结果为:MIN STEP IS 14。也就是,有一种方法,最多需要试摔14次。同时route数组打印出了方法。
比如route数组的结果:
route[i][2]
10 21 32 43 53
63 74 84 94 104
115 125 135 145 155
166 176 186 196 206
216 227 237 247 257
267 277 287 298 308
318 328 338 348 358
368 379 389 399 409
419 429 439 449 459
4610 4710 4810 4910 5010
5110 5210 5310 5410 5510
5611 5711 5811 5911 6011
6111 6211 6311 6411 6511
6611 6712 6812 6912 7012
7112 7212 7312 7412 7512
7612 7712 7812 7913 8013
8113 8213 8313 8413 8513
8613 8713 8813 8913 9013
9113 9214 9314 9414 9514
9614 9714 9814 9914 10014

因此,route[100][2]=14,第一次试摔是在14层,如果碎了,那么从第一层开始往14层试摔,如果没碎,第二次试摔是在route[100-14][2]=route[86][2]=13,也就是在14+13=27层进行第二次试摔,如果这次碎了,那么从第14+1=15层开始向上试摔,如果这次没碎,第三次试摔是在route[86-13][2]=route[73][2]=12,也就是在27+12=39层开始试摔,依次类推。
原文地址:点击打开链接
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值