先按允许高度排序,再用多重 01 背包判断哪些电梯高度可达。
OJ: luogu
题目 ID: P6771
难度:普及+/提高
标签:动态规划多重背包排序
日期: 2026-06-19 16:51
题意
有 n 种方块,每种方块有:
- 高度
h_i - 数量
c_i - 允许到达的最高高度
a_i
如果某个方块被放到高度 x 的位置,那么它整个方块的最高点不能超过 a_i。
因此,只有当当前堆叠高度足够低时,这种方块才能放上去。
目标是堆出尽可能高的太空电梯。
这张表把题意翻成了背包模型:
| 原题对象 | 含义 |
|---|---|
| 一种方块 | 有数量上限的物品 |
| 方块高度 | 物品重量 |
允许高度 a_i |
这类物品的使用上限 |
| 最高可达高度 | 目标答案 |
思路
先看最直接的暴力:
cpp
#include <bits/stdc++.h>
using namespace std;
struct Block {
int h;
int c;
int a;
};
int n;
vector<Block> blocks;
vector<int> choose_count; // choose_count[i] 表示第 i 种方块用了多少个
int answer = 0;
bool cmp_block(const Block &x, const Block &y) {
return x.a < y.a;
}
bool check() {
int cur_height = 0;
for (int i = 0; i < n; i++) {
cur_height += choose_count[i] * blocks[i].h;
if (cur_height > blocks[i].a) {
return false;
}
}
return true;
}
int calc_height() {
int height = 0;
for (int i = 0; i < n; i++) {
height += choose_count[i] * blocks[i].h;
}
return height;
}
// 暴力枚举每种方块用了多少次,叶子节点统一检查高度限制。
void dfs_choose(int dep) {
if (dep == n) {
if (check()) {
int value = calc_height();
if (answer < value) answer = value;
}
return;
}
for (int cnt = 0; cnt <= blocks[dep].c; cnt++) {
choose_count[dep] = cnt;
dfs_choose(dep + 1);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
blocks.resize(n);
for (int i = 0; i < n; i++) {
cin >> blocks[i].h >> blocks[i].a >> blocks[i].c;
}
sort(blocks.begin(), blocks.end(), cmp_block);
choose_count.assign(n, 0);
dfs_choose(0);
cout << answer << '\n';
return 0;
}brute.cpp 把每种方块用了多少个看成一层选择:choose_count[i] 表示第 i 种方块使用数量。递归先生成完整计数序列,叶子节点再检查每一层高度是否超出限制。
这个做法正确,但复杂度很高,只适合小数据验证。
关键观察是:
- 如果把方块按
a_i从小到大排序,那么前面处理的方块一定不会比后面更“宽松”。 - 对于某种方块,只能把它放在当前高度不超过
a_i - h_i的位置上。 - 因为每种方块有数量上限,所以是多重背包。
于是设:
dp[h]表示当前能否堆出高度h
这张表说明状态定义:
| 状态 | 含义 |
|---|---|
dp[h] |
高度 h 是否可达 |
对于每种方块:
- 如果它能放上去,就把当前可达高度再加上
h_i - 数量有限,所以同一种方块要重复做
c_i次 0/1 转移
DP 公式
设
把每种方块按高度上限从小到大处理。对第
最终答案为可达的最大高度:
公式解释:每类方块有高度上限和数量上限。按最大允许高度排序后,只在不超过当前上限的位置转移;重复做有限次 0/1 转移就表示数量限制。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
struct Block {
int h; // 方块高度
int c; // 数量
int a; // 允许到达的最高高度
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<Block> blocks(n);
int max_a = 0;
for (int i = 0; i < n; i++) {
cin >> blocks[i].h >> blocks[i].a >> blocks[i].c;
max_a = max(max_a, blocks[i].a);
}
sort(blocks.begin(), blocks.end(), [](const Block &x, const Block &y) {
return x.a < y.a;
});
// dp[h] = 当前能否堆出高度 h。
vector<char> dp(max_a + 1, 0);
dp[0] = 1;
for (const auto &blk : blocks) {
if (blk.h > blk.a) {
continue;
}
// 每种方块最多使用 c 次,所以重复做 c 次 0/1 转移。
for (int t = 0; t < blk.c; t++) {
for (int h = blk.a - blk.h; h >= 0; h--) {
if (!dp[h]) continue;
dp[h + blk.h] = 1;
}
}
}
for (int h = max_a; h >= 0; h--) {
if (dp[h]) {
cout << h << '\n';
break;
}
}
return 0;
}复杂度
- 时间复杂度:
,其中 A是最大允许高度 - 空间复杂度:
总结
这题的关键是排序:
- 先按
a_i从小到大处理 - 再把“能不能放”转成“当前高度是否足够低”
一旦状态写成“高度是否可达”,问题就变成了一个很标准的多重背包可达性 DP。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
