[USACO01OPEN] 垃圾陷阱
按时间排序后,用高度状态记录最晚存活时间,逐个垃圾做吃或堆的转移。
OJ: luogu
题目 ID: P1156
难度:普及+/提高
标签:动态规划排序
日期: 2026-06-19 18:02
题意
卡门掉进了垃圾井里,井深为 D。
一共有 G 个垃圾,第 i 个垃圾在时间 t_i 扔下,吃掉后可以增加 f_i 小时生命,堆起来可以增加 h_i 的高度。
卡门初始有 10 小时生命。她必须在垃圾到达时活着,才能选择把它吃掉或堆起来。
如果垃圾堆的总高度达到或超过 D,她就可以立刻逃出去。
要求输出最早的逃出时间;如果逃不出去,就输出她最长能活多久。
这张表把题意翻成了 DP 模型:
| 原题对象 | DP 含义 |
|---|---|
| 当前高度 | 状态 |
| 当前还能活到的最晚时间 | 状态值 |
| 吃掉一个垃圾 | 生命时间增加 |
| 堆起一个垃圾 | 高度增加 |
思路
一句话本质:按垃圾掉落时间排序后,在每个高度上维护"最晚还能活到什么时候"。每个垃圾选吃(延命)或堆(加高),一旦高度到达
先看最直接的做法:
import sys
data = list(map(int, sys.stdin.buffer.read().split()))
D, G = data[0], data[1]
garbages = []
idx = 2
for _ in range(G):
t, f, h = data[idx], data[idx + 1], data[idx + 2]
idx += 3
garbages.append((t, f, h))
garbages.sort()
best_escape = 10**9
max_life = 10
def dfs(i, health, height, cur_time):
global best_escape, max_life
if height >= D:
best_escape = min(best_escape, cur_time)
return
if i == G:
max_life = max(max_life, health)
return
t, f, h = garbages[i]
if health < t:
max_life = max(max_life, health)
return
dead_line = health
dfs(i + 1, health + f, height, t)
dfs(i + 1, health, height + h, t)
dfs(0, 10, 0, 0)
if best_escape != 10**9:
print(best_escape)
else:
print(max_life)brute.py 对每个垃圾递归分叉"吃"或"堆",
为什么必须先排序?
垃圾在不同时间落下,先掉落的垃圾必须先处理——你不可能在时间
排序后,每个垃圾只有两个选择,怎么设计状态?
当前的状态由"堆了多高"和"还能活到什么时候"决定。设
为什么存"绝对时间"而不是"剩余生命"?
垃圾掉落时间是绝对时间
吃和堆分别怎么转移?
- 吃:高度不变,生命
→ - 堆:生命不变,高度
→
其中
什么时候宣告逃出?
一旦
代码
#include <bits/stdc++.h>
using namespace std;
struct Garbage {
int t, f, h;
bool operator<(const Garbage& o) const {
return t < o.t; // 按掉落时间升序
}
};
const int MAXD = 105;
int D, G;
vector<Garbage> gb;
// dp[h] 表示当前堆叠高度为 h 时,奶牛的最大生命值。
int dp[MAXD];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> D >> G;
gb.resize(G);
for (int i = 0; i < G; i++) {
cin >> gb[i].t >> gb[i].f >> gb[i].h;
}
sort(gb.begin(), gb.end());
fill(dp, dp + D + 1, -1);
dp[0] = 10; // 初始生命为 10
for (int i = 0; i < G; i++) {
int t = gb[i].t, f = gb[i].f, h = gb[i].h;
vector<int> ndp(dp, dp + D + 1); // 旧状态备份
for (int j = 0; j <= D; j++) {
if (dp[j] < t) continue; // 活不到这个垃圾落下的时刻
int newh = j + h;
if (newh >= D) { // 堆叠高度足够,逃出
cout << t << '\n';
return 0;
}
ndp[newh] = max(ndp[newh], dp[j]); // 堆放:高度增加,生命不变
ndp[j] = max(ndp[j], dp[j] + f); // 吃掉:高度不变,生命增加
}
memcpy(dp, ndp.data(), (D + 1) * sizeof(int));
}
int ans = 10;
for (int j = 0; j <= D; j++) {
if (dp[j] >= 0) {
ans = max(ans, dp[j]);
}
}
cout << ans << '\n';
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
这里 G <= 100、D <= 100,所以复杂度非常小。
总结
这题的核心不是“能不能活”,而是“在每个高度上,最晚能活到什么时候”。
把垃圾按时间排序后,每个垃圾只有“吃”和“堆”两种决策,直接做高度 DP 就能得到最早逃出时间或最长存活时间。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
