动态规划之扔鸡蛋(或手机)问题

引入

2个鸡蛋,从100层楼上往下扔,以此来测试鸡蛋的硬度。比如鸡蛋在第9层没有摔碎,在第10层摔碎了,那么鸡蛋不会摔碎的临界点就是9层。

问:如何用最少的尝试次数,测试出鸡蛋不会摔碎的临界点?

分析

注意:题目的一个隐含在该最少次数下,一定能测出。

完美解决这个问题的思路是先逆向假设存在一个最优解x,第一次就应该从x层开始扔。为什么要从第x层开始扔呢?

假设第一次扔在第x+1层:如果第一个鸡蛋碎了,那么第二个鸡蛋只能从第1层开始一层一层扔,一直扔到第x层。这样一来,我们总共尝试了x+1次,和假设尝试x次相悖。由此可见,第一次扔的楼层必须小于x+1层。

假设第一次扔在第x-1层:如果第一个鸡蛋碎了,那么第二个鸡蛋只能从第1层开始一层一层扔,一直扔到第x-2层。这样一来,我们总共尝试了x-2+1 = x-1次,和假设尝试x次同样

假设第一次扔在第x层:如果第一个鸡蛋碎了,那么第二个鸡蛋只能从第1层开始一层一层扔(因为这是最后一个鸡蛋了,所以不能再冒险隔着楼层扔了),一直扔到第x-1层。这样一来,我们总共尝试了x-1+1 = x次,刚刚好没有超出假设次数。

接下来第二次该如何扔呢?首先明确,扔第一次的结果有两种,一是鸡蛋碎了,那么接下来从第一层开始往上扔。二是鸡蛋没碎,因为总共要扔x次,之前已经扔了一次,所以接下来就还要扔x-1次,那么问题就转变成了在100-x层高的楼上,最少扔x-1次该如何扔了。按照相同的思路,就在x-1层扔咯,这里的x-1对应就是实际的x+(x-1)层。按照这个思路一直重复操作,最后一次扔的层数应该是第100层,于是就有了如下的关系式。

x+(x-1)+(x-2)+...+1=100

共有x项,解出来x等于14,这便是最终的答案。

正题

N个鸡蛋和M层楼,要找到鸡蛋摔不碎的临界点,需要尝试几次?

分析

假设dp[N][M]等于最坏的运气下最多需要测试的次数,我们第一次扔在x层,那么有两种可能,若鸡蛋碎了,那么有dp[N][M]=dp[N-1][x-1]+1,若鸡蛋没碎,dp[N][M]=dp[N][M-x]+1。我们想求的是“运气最差”情况下,即在最优方案下最多需要测试的次数,所以dp[N][M]应该等于max(dp[N-1][x-1]+1,dp[N][M-x]+1)。而注意,x是一个不确定的数,它的取值范围为(0<=x<=M),x的取值决定了方案的优劣,若要方案最优,则需取最恰当的x值,即能令dp[N][M]最小,所以有

dp[N][M]=min{max(dp[N-1][x-1]+1,dp[N][M-x]+1) | 0<=x<=M}

上面的方程即为动态规划所需要的状态转移方程。

完整解题代码

#include<iostream>
#include<algorithm>
using namespace std;

//N个鸡蛋,M层楼 
void solve(int N,int M){
	if(N<1||M<1)	return;
	
//	定义记忆数组
	int dp[N+1][M+1];

//	初始化记忆数组,令其等于其楼层数	
	for(int i=1;i<=N;i++){
		for(int j=1;j<=M;j++){
			dp[i][j]=j;
		}
	}
	
	for(int i=2;i<=N;i++){
		for(int j=2;j<=M;j++){
			int tmp=dp[i-1][j];
			for(int x=1;x<=j;x++){
//				tmp存放1至x层时的最优方案次数 
				tmp=min(tmp,max(dp[i-1][x-1]+1,dp[i][j-x]+1));			
			}
			dp[i][j]=tmp;
		}
	}
	cout<<dp[N][M]<<endl;
}

int main(){
	solve(2,100);
	
	return 0;
}

优化

要得到第N个鸡蛋时的答案,其实只需要找到N-1个鸡蛋时的情况,不需要把第一个鸡蛋到第N个鸡蛋时的情况全部记录,所以记忆数组自需申请dp[2][M+1]的空间即可。这样可以降低空间复杂度。

同类型题目:测试次数

x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机。
各大厂商也就纷纷推出各种耐摔型手机。x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通。

x星球有很多高耸入云的高塔,刚好可以用来做耐摔测试。塔的每一层高度都是一样的,与地球上稍有不同的是,他们的第一层不是地面,而是相当于我们的2楼。

如果手机从第7层扔下去没摔坏,但第8层摔坏了,则手机耐摔指数=7。特别地,如果手机从第1层扔下去就坏了,则耐摔指数=0。如果到了塔的最高层第n层扔没摔坏,则耐摔指数=n。

为了减少测试次数,从每个厂家抽样3部手机参加测试。

某次测试的塔高为1000层,如果我们总是采用最佳策略,在最坏的运气下最多需要测试多少次才能确定手机的耐摔指数呢? 

请填写这个最多测试次数。

注意:需要填写的是一个整数,不要填写任何多余内容。

分析

这是第九届蓝桥杯的题目,基本是换了一个马甲,思路和扔鸡蛋问题是一模一样的。如果做过扔鸡蛋的题目,那么肯定可以很块做出来,可惜我没有。。。

完整解题代码

#include<iostream>
#include<algorithm>
using namespace std;

int main(){
//	3个手机,1000层楼,存储对应的测试次数 
	int dp[4][1001];
//	初始化记忆数组 
	for(int i=0;i<4;i++){
		for(int j=1;j<=1000;j++){
			dp[i][j]=j;
		}
	}
	
//	
	for(int i=1;i<4;i++){
		for(int j=1;j<=1000;j++){
//			tmp暂存1至x层时的最优方案次数,初始化值为第i-1个手机,j层楼时的测试次数 
			int tmp=dp[i-1][j];
			for(int x=1;x<=j;x++){
				tmp=min(tmp,max(dp[i-1][x-1]+1,dp[i][j-x]+1));
			}
			dp[i][j]=tmp;
		}
	}
	
	cout<<dp[3][1000]<<endl;
} 

运行结果是13。

 

 

本文参考了以下两篇微信推送

  1. 漫画:有趣的扔鸡蛋问题

  2. 漫画:动态规划解决扔鸡蛋问题

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值