测鸡蛋摔碎楼层的最少次数:动态规划

测鸡蛋摔碎楼层的最少次数:动态规划

问题描述

有n个鸡蛋,和一栋m层高的楼,欲测量鸡蛋最高从第几层楼摔下不会摔碎,问最小的测量次数

约定:假设鸡蛋极限耐受楼层是x,那么从 1 ~ x 层摔下,不会碎,从 x+1 ~ m 层摔下,碎

样例

样例输入1:

2 100

样例输出1:

14

样例输入2:

3 1000

样例输出2:

19

思路

这题动态规划。。。挺难想的

约定:
未知结果的楼层:在这一层摔下鸡蛋,不知道是碎还是不碎的楼层
已知结果的楼层:在这一层摔下鸡蛋,有唯一确定的已知结果
(如果在x层摔鸡蛋,没碎,那么1~x层的结果都是已知的,即:鸡蛋不会碎)
(如果在x层摔鸡蛋,碎了,那么x~m层的结果都是已知的,即:鸡蛋碎)

未知结果的楼层有10层,不是说从1 ~ 10楼未知,10表示一个数目,不是区间,10层未知,可以是20 ~ 30层楼,或是114~124层 … 但是我们不关心在第几层,我们关心的问题是有几层楼的结果是未知的

对于未知的x层楼,不管是在10 ~ 10+x 还是 30 ~ 30+x,我们测量的策略是不会变的,所以楼层在哪不重要,关键是剩余多少未知的楼层

假设现在有j层楼的结果是未知的,手中有i个鸡蛋,在第k层试摔一个鸡蛋,有两种可能:碎/不碎

  1. 如果碎了,说明k层以及往上的楼层,共j-k层楼,都会摔碎鸡蛋,结果是已知的,未知结果的楼层是剩下的前k-1层楼,那么问题转换为:
    【用手中的 i-1 个鸡蛋,测量未知结果的 k-1 层楼】的次数

  2. 如果没碎,说明k层以及往下的楼层,共k层楼,不会摔碎鸡蛋,结果是已知的,未知结果的楼层是剩下的后j-k层楼,问题转换为:
    【用手中的 i 个鸡蛋,测量未知结果的 j-k 层楼】的次数

因为在第k层楼测了一次,又因为要保证测出结果(或者说运气最坏的情况下),应该取情况1,2中最大的数值+1,就是【手里有i个鸡蛋,未知结果的楼层数目是j,在第k层未知结果的楼层测量】的答案

实际情况是这样的:

我们不一定只能在第k个未知结果的楼层上做上述测量,我们可以从第一,第二 … 一直到最后一层未知结果的楼层,都做一次测量

将k取遍1~j ,找到最小的结果,作为问题【手里有 i 个鸡蛋,未知结果的楼层数目是 j,求最小测量次数】的答案

于是自然地 写出状态转移方程

// dp[i][j]表示用i个样本,测量结果未知的j层楼,最少次数 
int dp[MAXLEN][MAXLEN];

// 手中i个鸡蛋,未确定结果的楼层有j层,在哪一层起手开始测试呢?
// k遍历所有未知结果的层: 1~j ,找到最小的结果
// 每个结果有2种情况:碎了/没碎,取最大的情况确保能够测出来 
int min = inf;
for(int k=1; k<=j; k++)
{
	// 每次测量有两个结果:碎,没碎
	// 如果碎了,这一层结果已知,用i-1个鸡蛋在未知的 前k-1层测
	// 如果没碎,这一层结果已知,用i个鸡蛋在未知的 后j-k层测 
	int res =  max(dp[i-1][k-1], dp[i][j-k]);
	if(res < min)
	{
		min = res;
	}
}
// 找到最小的结果
dp[i][j] = min + 1;

代码

dp数组的初始化同样重要:

如果当前未知结果的楼层数目为1,那么我们不管有多少个样本,只用测一次

如果当前样本数目为1,未知的楼层数目为j,那么只能乖乖从第1~j层逐步测,结果为j次

#include 

using namespace std;

#define MAXLEN 1145
#define inf 1145141919
// dp[i][j]表示用i个样本,测量结果未知的j层楼,最少次数 
int dp[MAXLEN][MAXLEN];
int n, m;

int main()
{
	cin>>n>>m;
	// 初始化:全为0,方便后续的max比较 
	for(int i=0; i<=n; i++)
	{
		for(int j=0; j<=m; j++)
		{
			dp[i][j] = 0;
		}
	}
	// 只有一层楼情况未知,不管多少样本,都只用测一次 
	for(int i=1; i<=n; i++)
	{
		dp[i][1] = 1;
	}
	// 只有一个样本,未知j层,只能一层一层往上测,测j次 
	for(int i=0; i<=m; i++)
	{
		dp[1][i] = i;
	}
	
	// dp 
	for(int i=2; i<=n; i++)
	{
		for(int j=2; j<=m; j++)
		{
			// 未确定结果的楼层有j层,在哪一层起手开始测试呢?
			// k遍历所有未知结果的层: 1~j ,找到最小的结果
			// 每个结果有2种情况:碎了/没碎,取最大的情况确保能够测出来 
			int min = inf;
			for(int k=1; k<=j; k++)
			{
				// 每次测量有两个结果:碎,没碎
				// 如果碎了,这一层结果已知,用i-1个样本在未知的 前k-1层测
				// 如果没碎,这一层结果已知,用i个样本在未知的 后j-k层测 
				int res =  max(dp[i-1][k-1], dp[i][j-k]);
				if(res < min)
				{
					min = res;
				}
			}
			dp[i][j] = min + 1;
		}
	}
	
	cout<<dp[n][m]<<endl;

	return 0;
}

你可能感兴趣的:(算法)