例四:扔鸡蛋问题
定义鸡蛋的硬度为 k,则代表鸡蛋最高从 k 楼扔下来不会碎掉,现在给你 n 个硬度相同的鸡蛋,楼高为 m,问最坏情况下最少测多少次,可以测出鸡蛋的硬度。
我们假设有2个鸡蛋,100层楼。那么我们应该如何扔在最坏情况下测的次数最少呢?
1.二分法:刚开始我傻傻的以为是二分法,第一个鸡蛋在第50层扔,但是如果一个鸡蛋在第50层碎了,那么第二个鸡蛋只能从第1层扔直到第49层,才能测出鸡蛋的硬度(在最坏情况为49层时),这样最坏情况下需要测50次
2.平方根法:我们继续优化,我们每10层测一次,第10层若碎了,第二个鸡蛋就从1层测到9层。若没碎继续测第20层…以此类推,最坏情况是第99层时,这时我们需要测10 + 9 = 19次
3.假设法:方法二一定是最优解吗?我们可以假设一下,设2个鸡蛋100层楼在最坏情况下最多测x次,那么第一个鸡蛋应该放在第x层最合适(如果第一个鸡蛋放在是x + i层,如果碎了最坏情况下最多测x + i次,比x次多;如果第一个鸡蛋放在x - i层,如果没碎的话,由于x - i比x小,所以它剩余测的次数一定 >= x)
因此,我们第一次放在x层(碎了话最多测x次,没碎的话第二次放在2 * x - 1层,碎的话最多测x次,没碎的话第三次放在3 * x - 3层,碎的话最多测x次,没碎的话第四次放在4 * x - 6层…依次类推),我们只要保证x + (x - 1) + (x - 2) … + 1 >= 100即可,这样解出最优答案为14
好了,以上是推理,我们可以用dp的方法来求出正确的答案。我们可以假设dp[i][j]
代表i个鸡蛋j层楼最少测多少次。这样我们可以得到:
初始化:
dp[1][j] = k (1 <= j <= m) //一个鸡蛋j层楼最坏情况下最少需要测j次
dp[i][1] = 1 (1 <= i <= n) //i个鸡蛋1层楼最坏情况下最少需要测1次
这样我们可以得到dp转移式:
dp[i][m] = min(max(dp[i - 1][j - 1], dp[i][m - j])) (1 <= j <= m)
那么,当n范围为1e2,m范围为1e9的时候,我们怎么办呢?
数组肯定开不下那么大,而且上面算法的时间复杂度为O(n(m^2)),会直接爆掉的。这时我们就可以进行重新定义状态信息来优化算法。
我们可以假设dp[i][j]
代表i个鸡蛋测j次最多能测多少层楼。这样我们可以得到:
//初始化:
dp[1][j] = j //一个鸡蛋测j次最多可以测j层楼
dp[i][1] = 1 //i个鸡蛋测1次最多可以测1层楼
我们假设第i个鸡蛋测k次最多测n层楼,那么在测n层楼的情况下,第一次需要测的位置应该是i-1个鸡蛋测k - 1次最多测的层数+1(只有满足这个条件,在第m层碎了测的次数最坏情况才为k)
若在第m层没碎,那么往上最多能测的层数应该是i个鸡蛋测k - 1次最多的层数
因此,dp[i][k] = dp[i - 1][k - 1] + dp[i][k - 1] + 1
我们只要找第一个dp[n][j]大于m的j即是正确答案
最后上AC代码:
#include <stdio.h>
#define ll long long
ll dp[35][100005] = {0};
int main () {
ll n, m;
scanf("%lld%lld", &n, &m);
if (n == 1) {
printf("%lld\n", m);
} else {
for (int i = 1; i <= 100000; i++) {
dp[1][i] = i;
}
for (int i = 1; i <= n; i++) {
dp[i][1] = 1;
}
for (int i = 2; i <= n; i++) {
for (int j = 2; j <= 100000; j++) {
dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1] + 1;
}
}
for (int j = 1; j <= 100000; j++) {
if (dp[n][j] >= m) {
printf("%d\n", j);
break;
}
}
}
return 0;
}
如果有写的不对或者不全面的地方 可通过主页的联系方式进行指正,谢谢