把每块木石看成只能用一次的物品,按体积做最小代价背包,并把超过目标体积的状态统一截断到 v。
OJ: luogu
题目 ID: P1510
难度:普及/提高-
标签:动态规划01背包背包
日期: 2026-06-19 14:27
题意
东海还需要至少 v 的体积才能填平。
现在有 n 块木石,每块木石有:
- 体积
k - 运送它需要的体力
m
精卫还剩 c 点体力。每块木石最多使用一次,问:
- 如果能填平东海,最多还能剩多少体力
- 如果不能填平,输出
Impossible
思路
一句话本质:体力是容量,石头体积是价值。用 01 背包求出每种体力花费下的最大体积,找到第一个达到
先看最直接的暴力:
import sys
data = list(map(int, sys.stdin.buffer.read().split()))
v_target, n, c = data[0], data[1], data[2]
stones = []
idx = 3
for _ in range(n):
k, m = data[idx], data[idx + 1]
idx += 2
stones.append((k, m))
best_vol = 0
best_stamina = 0
for mask in range(1 << n):
vol = 0
stamina = 0
for i in range(n):
if mask >> i & 1:
k, m = stones[i]
vol += k
stamina += m
if stamina <= c:
if vol > best_vol or (vol == best_vol and stamina < best_stamina):
best_vol = vol
best_stamina = stamina
if best_vol >= v_target:
print(c - best_stamina)
else:
print('Impossible')brute.py 枚举
暴力里的选择和 01 背包有什么对应关系?
每块石头只能用一次→0/1 物品。体力消耗
但题目只要"至少达到
一旦体积已经
为什么要找最小体力花费,而不是直接求剩余体力?
题目问的是"填平后最多剩多少体力",等价于在能达到
具体来说,设
然后对全体
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXC = 10005;
int v_target, n, c;
// dp[j] 表示花费 j 体力能填的最大体积。
int dp[MAXC];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> v_target >> n >> c;
fill(dp, dp + c + 1, 0);
// 0/1 背包:体力是容量,石头体积是价值。
for (int i = 1; i <= n; i++) {
int k, m;
cin >> k >> m;
for (int j = c; j >= m; j--) {
dp[j] = max(dp[j], dp[j - m] + k);
}
}
// 找到最小的体力花费使填的体积 ≥ 目标。
for (int j = 0; j <= c; j++) {
if (dp[j] >= v_target) {
cout << c - j << '\n'; // 剩余体力
return 0;
}
}
cout << "Impossible" << '\n';
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
总结
这题的关键不是普通的“最大价值背包”,而是:
- 至少达到一个阈值
- 并且让代价最小
遇到这种“达到目标就行、超过没有额外收益”的题时,要想到把状态截断到目标值,避免无意义地继续扩张状态空间。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
