鸡蛋的硬度

GitHub跳转原题关系图返回列表

扔鸡蛋问题:f[i][j] 为 i 层楼、j 个蛋最坏情况的最少扔蛋次数,枚举首扔位置 x,碎则测下层段且蛋减一、不碎测上层段蛋不变,max 套 min,预计算全表后每组询问 O(1) 查表。

OJ: roj

题目 ID: 1300

难度:普及-

标签:动态规划区间DPpython

创建: 2026-09-30 03:53

更新: 2026-10-05 08:33

题目描述

楼高 nn 层,手里有 mm 个硬度相同的鸡蛋:硬度 xx(0⩽x⩽n0 \leqslant x \leqslant n)满足从 ⩽x\leqslant x 层扔不碎(蛋可继续用)、从 >x> x 层扔必碎。要设计一个测试方案,无论真实硬度是多少都能把它唯一确定出来,求最坏情况下所需的最少扔蛋次数。

输入包括多组数据,每组数据一行两个正整数 nn 和 mm(1⩽n⩽1001 \leqslant n \leqslant 100,1⩽m⩽101 \leqslant m \leqslant 10),分别表示楼高和蛋数;对每组数据输出一行最优策略在最坏情况下的扔蛋次数。样例输入 100 1 / 100 2,对应输出 100 / 14。

思路

一次扔蛋把问题切成两个更小的同类问题:在 xx 层扔一次,碎了剩 x−1x-1 层、j−1j-1 个蛋,不碎剩 i−xi-x 层、jj 个蛋,于是定义 f[i][j]f[i][j] 为 ii 层楼、jj 个蛋最坏情况下的最少扔蛋次数,转移为 f[i][j]=1+min⁡xmax⁡(f[x−1][j−1], f[i−x][j])f[i][j] = 1 + \min_x \max(f[x-1][j-1],\ f[i-x][j])。边界是 f[0][j]=0f[0][j]=0、f[i][1]=if[i][1]=i(单蛋只能逐层往上扔,跳层碎了就无法区分)。预计算 O(n2m)O(n^2 m) 的全表后每组询问 O(1)O(1) 查表输出。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 03:53
# update_at: 2026-09-30 03:53

import sys

# 答案上界:100 层楼、1 个鸡蛋时最坏要扔 100 次
MAX_FLOORS = 100
MAX_EGGS = 10


def build_min_drops() -> list[list[int]]:
    """预计算 f[i][j]:i 层楼、j 个鸡蛋在最坏情况下的最少扔蛋次数。

    f[i][1] = i(只有一个鸡蛋只能从低到高逐层试);
    j>=2 时在 x 层扔一次:碎了测 [1,x-1] 剩 j-1 个蛋,
    没碎测 [x+1,i] 仍 j 个蛋,取两者最坏再 +1,x 取最小。
    """
    f = [[0] * (MAX_EGGS + 1) for _ in range(MAX_FLOORS + 1)]
    for floors in range(1, MAX_FLOORS + 1):
        f[floors][1] = floors                       # 单蛋:逐层往上扔
        for eggs in range(2, MAX_EGGS + 1):
            # 枚举第一次扔的位置 x(1..floors),取最坏子问题较小者
            f[floors][eggs] = 1 + min(
                max(f[x - 1][eggs - 1], f[floors - x][eggs])
                for x in range(1, floors + 1)
            )
    return f


def solve() -> None:
    f = build_min_drops()
    pairs = [list(map(int, line.split())) for line in sys.stdin if line.strip()]
    print('\n'.join(str(f[n][m]) for n, m in pairs))


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-05 08:33
 * update_at: 2026-10-05 08:33
 */
// 扔鸡蛋:f[i][j] = i 层楼、j 个蛋,最坏情况下最少扔蛋次数
// 在 x 层扔一次:碎 -> 剩 x-1 层、j-1 个蛋;不碎 -> 剩 i-x 层、j 个蛋
// 对手挑更糟的分支(max),我方挑最优的首扔位置(min)

#include <cstdio>

typedef long long ll;

const int MAXN = 100;   // 楼高上限
const int MAXM = 10;    // 蛋数上限

ll f[MAXN + 5][MAXM + 5]; // f[楼层数][蛋数] = 最坏情况最少扔蛋次数

int main() {
    // 按楼层数从小到大、蛋数从小到大填表,两个子分支都已算好
    for (int j = 0; j <= MAXM; ++j) f[0][j] = 0; // 0 层楼不用扔
    for (int i = 1; i <= MAXN; ++i) {
        f[i][1] = i; // 单蛋只能从低到高逐层试
        for (int j = 2; j <= MAXM; ++j) {
            f[i][j] = MAXN; // 先放一个上界
            for (int x = 1; x <= i; ++x) {
                ll worst = f[x - 1][j - 1];
                if (f[i - x][j] > worst) worst = f[i - x][j];
                if (worst + 1 < f[i][j]) f[i][j] = worst + 1;
            }
        }
    }

    // 多组询问直接 O(1) 查表
    int n, m;
    while (scanf("%d %d", &n, &m) == 2) {
        printf("%lld\n", f[n][m]);
    }
    return 0;
}