题目:
给你
n
个鸡蛋,和一栋从1
到 m 共有m
层楼的建筑。你需要测试鸡蛋的耐摔度 也就是鸡蛋从第几层扔下去恰好会碎
如果鸡蛋的耐摔度是 F 那么任何从高于
F
的楼层落下的鸡蛋都会碎,从F
楼层或比它低的楼层落下的鸡蛋都不会破。你每次测试可以拿一个鸡蛋,把它从任意高度的楼层 扔下(没摔碎的话 还可以继续使用)
你的目标是测试出
F
的值是多少。要求在最坏的情况下 用尽量少的测试次数来找到鸡蛋的耐摔度
解题思路:
1、首先我们要理解题目意思,根据题意,我们很容易想到一种完成任务的笨方法,那就是拿一个鸡蛋,从一楼开始慢慢的往上层测试。这个方法可以只用一个鸡蛋就能找到耐摔度。但是需要的测试次数最大是N(如果N层也摔不碎 那你需要n次)
2、如何优化次数呢?可能很容易联想到二分查找,
我们假设有100层楼,给你两个鸡蛋。
用二分查找的话,我们首先在第50层楼扔,如果鸡蛋不碎的话,到第75层扔。如果50层碎了的话,那么你只剩下一个鸡蛋了,所以你不能浪了,只能使用第一个思路,从第一层开始慢慢往上找,这样才能保证你能找到临界楼层。
题目上说了,要我们考虑最坏情况下的最小次数,那么用二分查找的话,最坏情况就是如果临界层数在49层的话,我们要尝试50次。这个方法并不是最优秀的方法。
3、dp
先假设情况:2个鸡蛋,100层楼,求最坏的情况下,需要最少几次次测试才能找到鸡蛋的耐摔度
我们假设答案是x.(最坏需要x次)
然后我们首先在第x层扔鸡蛋 会有两种结果
1、碎了 若碎了 剩下的一个鸡蛋只能从1-(x-1)遍历尝试了
2、没碎 若没碎 那么第二次就在x+(x-1)楼层摔。
为什么是x+x-1楼层呢?
首先我们已经假设了通过x步我们就能得到答案,现在我们在x层已经用了一次了,那么就只剩下x-1步了。所以我们选择x+(x-1)层,如果碎了,我们就能通过x-2步,遍历x+1 ~ x+(x-1)-1的所有楼层。
依次类推。
如果在x+(x-1)楼碎了,那么同1,遍历x+1~x+(x-1)-1
如果没碎,那么同2,就在x+(x-1)+(x-2)层摔
.....................................
可以看到,每次我们增加的楼层都是前一次减1.所以我们还要保证的就是应该在增加的层数变成0之前到顶楼,
所以有: x+(x-1)+…+1≥100
等差数列 求和之后 可以化简为 x^2+x-200≥0 解出来 x是14
显然 14比我们二分的最坏情况50次要少 这就是正确答案。
那么 现在跳出我们假设的题目 推一下普适的方法
n个鸡蛋,m层楼
f[ n ][ m ]代表 最坏的情况下,找到鸡蛋的耐摔度需要的最小次数
状态转移方程为:f[n][m] = min { 1 + max ( f [ n-1 ][ k-1 ] , f [ n ][ m-k ] ) } k属于[1,m-1]
为什么是这样呢?
我们一层一层来看 先看内层
max ( f [ n-1 ][ k-1 ] , f [ n ][ m-k ] )
n个鸡蛋的时候,我们选择第k层往下摔
如果破了,那么手里只有n-1鸡蛋了,那么就需要测试f[n-1][k-1]楼层。
如果没破,那么手里还有n个鸡蛋,那么需要测试k+1 ~ m这些楼层。也就是f[n][m-k]。
而无论破没破 都算一次测试了 所以不管破没破 都要 + 1
而为什么取最大值呢?因为题目说了 要最坏情况 所以我们取这两者中步数多的
而k怎么选呢 再看总的式子
f[n][m] = min { 1 + max ( f [ n-1 ][ k-1 ] , f [ n ][ m-k ] ) } k属于[1,m-1]
k的选取范围是[1,m-1] 而我们目的是想测试次数最少 所以我们在这里面选一个会使结果最小的k值 所以外边枚举k,取min
至此 整个思路就完成了
初始话的时候 给整个二维数组都初始化为最坏情况 然后dp就好了
ps:补充一点 max ( f [ n-1 ][ k-1 ] , f [ n ][ m-k ] ) 有人认为没必要选最大 越高层越容易碎 所以直接选低层
这是不对的 要注意 确实越高层越容易碎 但是我们要求的是次数最小 跟碎不碎没关系 碎不碎都要算次数的 如果你想不碎 那直接从一层开始 一个鸡蛋就够了 所以 要明白我们要求的是什么
代码:
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
#include<iostream>
using namespace std;
const int INF=0x3f3f3f3f;
int a[105][10005];
int n,m,minn;
int main(){
for(int i=1;i<=100;i++){//初始化
for(int j=1;j<=10000;j++){
a[i][j]=j;
}
}
scanf("%d%d",&n,&m);//n个鸡蛋 m层楼
for(int i=2;i<=n;i++){
for(int j=2;j<=m;j++){
for(int k=1;k<=j-1;k++){
a[i][j]=min(a[i][j],1+max(a[i-1][k-1],a[i][j-k]));
}
}
}
printf("%d\n",a[n][m]);
}
参考文档:
https://blog.csdn.net/wolinxuebin/article/details/47057707
http://www.cnblogs.com/Matrix_Yao/p/4793823.html