1 问题引入
普通背包只有一种「费用」——容量。但有些限制是两种同时存在的:
定义二维费用背包
有
样例(两种费用分别叫「重量」和「体积」):
4 5 6
2 3 4
3 2 5
1 1 2
4 4 7
2 加一维就行
费用多了一种,状态就多一维。设
和 01 背包的式子一模一样,只是每处多了一个下标。
压掉
// 二维费用背包:每件物品消耗两种费用 a、b,两个上限分别 A、B
// f[i][j] = 两种费用分别花 i、j 时的最大价值;两维都倒序,保证每件只用一次
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 5;
int n, A, B;
int f[maxn][maxn];
int main() {
cin >> n >> A >> B;
for (int i = 1; i <= n; ++i) {
int a, b, v;
cin >> a >> b >> v;
for (int j = A; j >= a; --j) { // 第一维倒序
for (int k = B; k >= b; --k) { // 第二维倒序
f[j][k] = max(f[j][k], f[j - a][k - b] + v);
}
}
}
cout << f[A][B] << endl;
return 0;
}
输出:
9
说明两个维度都要倒序
一维背包倒序是为了「
如果物品是完全背包类型(可用无限次),把两个维度都改成正序;如果是多重背包,按多重背包的方式拆分。
3 「件数」也是费用
有一类题目不明说「二维费用」,而是限制最多选几件:
有
件物品和一个容量为 的背包,最多只能选 件,求最大价值。
这等价于给每件物品加了一种费用——「件数」,每件的件数费用恒为
于是状态自然写成
// 「件数」当成第二种费用:最多选 K 件,容量 C
// f[cnt][j] = 选 cnt 件、容量 j 时的最大价值,两维都倒序
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 5;
int n, C, K;
int f[maxn][maxn];
int main() {
cin >> n >> C >> K;
for (int i = 1; i <= n; ++i) {
int w, v;
cin >> w >> v;
for (int cnt = K; cnt >= 1; --cnt) { // 件数倒序
for (int j = C; j >= w; --j) { // 容量倒序
f[cnt][j] = max(f[cnt][j], f[cnt - 1][j - w] + v);
}
}
}
cout << f[K][C] << endl;
return 0;
}
样例输入:
5 10 2
3 5
4 7
2 3
5 9
1 2
输出:
16
4 复整数视角
《背包问题九讲》里有一个挺有意思的类比:把「费用」看成数,一维背包的费用是自然数,二维背包的费用是复整数
也就是说,二维费用背包并没有引入新东西,只是把一维问题里的「数」换成了「二维向量」,而所有运算(加法、比较)都逐分量进行。
说明这个视角有什么用
它提示了一个通用的改造方法:当题目多出一种约束时,先问「能不能把它写成一个新的费用维度」。能写成费用维度的,状态加一维就能解;写不成的(比如依赖关系、互斥关系),才需要换模型(分组背包、树形 DP)。
5 小结
二维费用背包的结论很短:加一维,两个维度都倒序。
但「件数也是费用」这个观察值得记住——很多看起来是新题目的限制,其实只是换了个名字的费用。
6 相关题解
暂无相关题解