动态规划之——鹰蛋

文章讨论了一个教授如何通过最少的试验次数找出鹰蛋从哪层楼扔下不会碎的问题,利用动态规划和递推算法优化计算过程,解决M个蛋和N层楼的最大试验次数问题。
摘要由CSDN通过智能技术生成

题目描述

有一个教授有一批一模一样的鹰蛋。有一天他来到了一栋楼的脚下,他突然想知道自己的鹰蛋从这栋楼的多少层扔下时恰好不碎。
一颗鹰蛋如果从i层摔下没有碎,那么从小于j层摔下也不会碎,如果从j层摔下碎了,从大于j层摔下也会摔碎。如果恰好存在一层n,从n层摔下鹰蛋未碎,而从n+1层摔下碎了,那么这批鹰蛋恰好从n层摔下未碎。如果从第一层摔下碎了,那么称恰好从0层摔下未碎;另一方面,如果从最高层(N层)摔下未碎,那么称恰好从N层摔下未碎
这个教授想知道从第多少层恰好摔下不碎,但是这个教授想使用最少的试验次数来得到这个值。
现已知鹰蛋的个数M和楼层高度N,试问在最坏情况下,这个教授最少需要试验多少次来得到他想要的结果?
比如:M为1,N为3。那么这个教授为了得到结果,就必须从一层一层测试,在最坏情况下,最少需要3次试验。但是如果M=2,N=3,那么他就可以第一次从二层扔下,不管碎了还是没碎,他都只需再扔一次即可得到结果,即需要做2次试验即可。

关于输入

有多组输入,每一组输入单独一行。
分别为两个如题所述的正整数N(大于0小于400001),M (大于0小于N+1)中间用空格隔开。
如果得到的N和M都为0,表示输入结束。

关于输出

每组输出单独一行,输出需要试验的次数K。

分析

首先,如果不考虑N特别大的情况的话,我们不难看出:

1、当M=1时,只有一个蛋,此时,为了确保蛋碎之前能够确定准确楼层,我们就只能从第一层楼开始一层一层往上扔,直到蛋碎或者达到最高楼层,若考虑最坏情况,则需要N次;

2、当N=0时,楼层数为0,此时,不需要再扔蛋,所以次数为0;

如果使用一个数组f[M][N]来记录在有N层、M个蛋时,考虑最坏情况,最少需要扔蛋的次数的话,根据上面的论断,我们可以得到以下关系式:

f[1][i] = i;

f[i][0] = 0;

那么我们应该如何利用上面的边界条件来得出任意的f[M][N]呢?

我们假设第一次在第i层楼扔蛋,有两种情况:第一种,蛋没碎,说明目标楼层在下方的i - 1层楼中,此时还剩M个蛋,于是,所求次数为1 + f[M][i - 1];第二种,蛋碎了,说明目标楼层在上方的N - i层楼中,此时还剩M - 1个蛋,于是,所求次数为1 + f[M - 1][N - i]。综合以上两种情况,我们可以得到,f[M][N] = min(max(1 + f[M][i - 1], 1 + f[M - 1][N - i]) (i = 1, 2,……,N))。

代码不难给出:

#include<iostream>
using namespace std;
int n, m;//n为楼层数,m为蛋数
int f[200][200];
bool w[200][200]{ 0 };//动规部分,用来判断对应的f是否被计算过,以减少计算量
int max1(int a, int b) {
	if (a >= b) {
		return a;
	}
	else {
		return b;
	}
}
int calc(int n, int m) {
	if (w[n][m]) {
		return f[n][m];
	}//如果f[n][m]已经计算过,就直接调用先前计算过的值
	int i, j;
	if (n == 0) {
		return 0;
	}//n == 0时,所需次数为0
	else if (m == 1) {
		return n;
	}//m == 0时,所需次数即楼层数
	else {
		f[n][m] = 10000;
		int temp;
		for (i = 1; i <= n; i++) {
			temp = max1(calc(i - 1, m - 1), calc(n - i, m)) + 1;
			if (temp < f[n][m]) {
				f[n][m] = temp;
			}
		}//遍历所有i,以得到最小的扔蛋次数
		w[n][m] = 1;//计算过后w[n][m]变为1
		return f[n][m];
	}
}
int main() {
	while (cin >> n >> m) {
		cout << calc(n, m) << endl;
	}
}

可以注意到,在上方的代码中,数组f[M][N]只开到了200*200,这是因为当M,N很大的时候,上方代码中的算法会消耗过多的时间,并不适用。

因此,为了解决M,N很大的情况,我们需要换一种想法。

我们不妨定义一个新的数组g[i][j],用以表示在有j个蛋的情况下,尝试i次最多可以确定的楼层数,我们考虑在某一层楼扔下一个蛋,仍有两种情况:第一种,蛋没碎,此时,我们需要向上确定楼层数,能够向上确定的楼层数为g[i - 1][j];第二种,蛋碎了,此时,我们需要向下确定楼层数,能够向下确定的楼层数为g[i - 1][j - 1]。于是,我们可以得到:g[i][j] = g[i - 1][j] + g[i - 1][j - 1] + 1。

同时,我们也不难发现,当尝试次数为1时,我们只能确定1层楼(因为只能扔一次蛋);当只有1个蛋时,我们只能确定i层楼(把这一个蛋从下往上一层一层扔)。所以有:g[1][j] = 1; g[i][1] = i。

为了减少计算量,我们发现,当蛋数足够时,最优的扔蛋方式即为二分法,此时,次数为\log_{2}N,所以,我们可以不用考虑蛋数超过log_{2}N的情况,这样就大大减少了计算量。

代码不难给出:

#include<iostream>
#include<string.h>
#include<cmath>
#include<algorithm>
#include<iomanip>
using namespace std;
int n, m;
int g[40010][20];
int i, j;
int main() {
	for (i = 1; i < 40001; i++) {
		g[i][1] = i;
	}//当蛋数为1时,最多可以确定i层楼
	for (i = 1; i < 20; i++) {
		g[1][i] = 1;
	}//当尝试次数为1时,只能确定1层楼
	for (i = 2; i < 40001; i++) {
		for (j = 2; j < 20; j++) {
			g[i][j] = g[i - 1][j - 1] + g[i - 1][j] + 1;关系式
		}
	}
	while (cin >> n >> m) {
		if (n == 0 && m == 0) {
			break;
		}
		else {
			if (m >= 19) {
				m = 19;
			}
			for (i = 1; i < 40010; i++) {
				if (g[i][m] >= n) {
					break;
				}
			}
			cout << i << endl;
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值