这是一份关于经典算法问题“鸡蛋掉落问题(Egg Dropping Puzzle)”的代码分析。
1. 题目描述 (Markdown 格式)
题目名称:鸡蛋掉落问题
问题描述: 现有 $n$ 层楼和 $m$ 个鸡蛋。你需要确定一个临界楼层 $F$($0 \le F \le n$),使得在 $F$ 层或以下掉落鸡蛋不会碎,而在 $F$ 层以上掉落则会碎。 当你丢下一个鸡蛋: 1. 如果鸡蛋碎了,你就不能再使用它,且你需要检查该层以下的楼层。 2. 如果鸡蛋没碎,你可以继续使用它,且你需要检查该层以上的楼层。
目标: 求在最坏情况下,最少需要实验多少次才能确定临界楼层 $F$。
输入: 两个正整数 $n$(楼层数)和 $m$(鸡蛋数)。
输出:
第一行:使用递归函数 f(n, m) 计算的结果。
第二行:使用动态规划函数 g(n, m) 计算的结果。
2. 代码注释与逻辑分析
#include <algorithm>
#include <iostream>
#include <limits>
using namespace std;
const int MAXN = 105;
const int MAXK = 105;
int h[MAXN][MAXK]; // 存储动态规划状态的数组,h[i][j] 表示 i 层楼 j 个鸡蛋的最少实验次数
// 递归方案:未优化(无记忆化),存在大量重复计算
int f(int n, int m)
{
if (m == 1) return n; // 只有一个鸡蛋,必须从 1 层逐层向上尝试,最坏需要 n 次
if (n == 0) return 0; // 0 层楼不需要实验
int ret = numeric_limits<int>::max();
// 遍历在第 i 层丢下鸡蛋的所有可能性
for (int i = 1; i <= n; i++)
// max(...) 代表最坏情况:
// f(i-1, m-1): 鸡蛋碎了,剩下 m-1 个鸡蛋,需检查下面 i-1 层
// f(n-i, m): 鸡蛋没碎,剩下 m 个鸡蛋,需检查上面 n-i 层
// +1 代表本次实验
ret = min(ret, max(f(n - i, m), f(i - 1, m - 1)) + 1);
return ret;
}
// 动态规划方案:自底向上
int g(int n, int m)
{
// 初始化基本状态
for (int i = 1; i <= n; i++)
h[i][1] = i; // 只有 1 个鸡蛋时,i 层楼需要 i 次
for (int j = 1; j <= m; j++)
h[0][j] = 0; // 0 层楼需要 0 次
// 状态转移过程
for (int i = 1; i <= n; i++){ // 遍历楼层
for (int j = 2; j <= m; j++){ // 遍历鸡蛋数
h[i][j] = numeric_limits<int>::max();
for (int k = 1; k <= i; k++) // 遍历第一次抛掷的楼层 k
h[i][j] = min(
h[i][j],
max(h[i - k][j], h[k - 1][j - 1]) + 1
);
}
}
return h[n][m];
}
int main()
{
int n, m;
cin >> n >> m;
// 输出两遍结果,逻辑上 f 和 g 是等价的
cout << f(n, m) << endl << g(n, m) << endl;
return 0;
}
3. 状态转移与复杂度分析
状态转移方程
设 $dp[i][j]$ 为 $i$ 层楼、$j$ 个鸡蛋时的最优解: $$dp[i][j] = \min_{1 \le k \le i} { \max(dp[i-k][j], dp[k-1][j-1]) + 1 }$$
复杂度分析
- 递归函数
f(n, m): - 由于没有使用记忆化搜索,其复杂度呈指数级增长。
- 对于 $n=100, m=100$,递归深度和分支数会导致运行时间极长(无法在正常时间内跑完)。
- 动态规划
g(n, m): - 时间复杂度:$O(n^2 \cdot m)$。共有 $n \times m$ 个状态,每个状态需要遍历 $O(n)$ 次来寻找最优抛掷点。
- 空间复杂度:$O(n \cdot m)$。使用了一个二维数组存储状态。
4. 题目解析
判断题
-
当输入为 7 3 时,第 19 行用来取最小值的 min 函数执行了 449 次。
- 答案:B (错误)
- 解析:设 $C(n, m)$ 为
min执行次数。 根据递归式 $C(n, m) = n + \sum_{i=1}^n (C(n-i, m) + C(i-1, m-1))$。 已知 $C(n, 1) = 0, C(0, m) = 0$。 计算得:$C(n, 2) = 2^n - 1$。 $C(7, 3) = 2 \cdot C(6, 3) + 2^6 = 2(192) + 64 = 448$。 实际执行了 448 次而非 449 次。
-
输出的两行整数总是相同的。
- 答案:A (正确)
- 解析:
f是递归实现,g是迭代实现,两者逻辑完全一致,均解同一个方程。
-
当 m 为 1 时,输出的第一行总为 n。
- 答案:A (正确)
- 解析:代码第 14 行
if (m == 1) return n;直接处理了此基准情况。
单选题
-
算法 g(n, m) 最为准确的时间复杂度分析结果为( )。
- 答案:C ($O(n^2 m)$)
- 解析:如前分析,外层两层循环 $n \times m$,内层查找 $k$ 经历 $n$ 次,总复杂度 $O(n \cdot m \cdot n) = O(n^2 m)$。
-
当输入为 20 2 时,输出的第一行为( )。
- 答案:C (6)
- 解析:对于 2 个鸡蛋,$x$ 次实验最高能测出的楼层公式为 $\frac{x(x+1)}{2}$。 若 $x=5$,$\frac{5 \times 6}{2} = 15 < 20$; 若 $x=6$,$\frac{6 \times 7}{2} = 21 \ge 20$。因此最少需要 6 次。
-
当输入 100 100 时,输出的第一行为( )。
- 答案:B (7)
- 解析:当鸡蛋数 $m$ 非常充足时($m \ge \log_2 n$),最优策略是二分查找。 $\log_2 100 \approx 6.64$。 $2^6 = 64 < 100$,$2^7 = 128 > 100$。 因此对于 100 层楼,只要鸡蛋够多,最坏情况下 7 次必出结果。
—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com