动态规划-背包问题¶
参考资料:
崔添翼——背包九讲
背包问题整理 https://www.jianshu.com/p/224062fb6ce5
完全背包https://blog.csdn.net/u013480600/article/category/2656071
01背包https://blog.csdn.net/u013480600/article/category/2160339
多重背包https://blog.csdn.net/u013480600/article/category/2668007
OI wiki的总结(背包DP):https://oi-wiki.org/dp/knapsack/
B站闫雪灿:https://www.bilibili.com/video/BV1Zc4118723
https://www.bilibili.com/video/BV1qt411Z7nE
01背包¶
O(n^2)空间复杂度算法¶
题面形式:
有n个重量和价值分别为w_i, v_i的物品,从这些物品中选出总重量不超过W的物品,每个物品最多只能选一次,求价值总和最大的方案。进一步思考,如果需要输出方案怎么办?(也就是究竟选了哪些物品)
这里需要提一下,并不是题面符合此形式就一定用01背包来求解,要去看数据范围,如果数据范围很大但是n的数量很小,那么可以考虑《挑战程序设计竞赛》折半枚举里面的超大背包问题。
先考虑一下暴力解法:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n = 20, totalWeight = 20;
vector<int> w(n), v(n);
int zeroOnePack(int i, int weightLeft)
{
int res;
if (i == n) res = 0; //没有剩余物品了
else if (weightLeft < w[i])
res = zeroOnePack(i + 1, weightLeft); //剩余容量放不下当前物品,考虑下一个
else //当前物品可以放下,选和不选都尝试一下
res = max(zeroOnePack(i + 1, weightLeft), zeroOnePack(i + 1, weightLeft - w[i]) + v[i]);
return res;
}
int main()
{
cin >> n >> totalWeight;
for (int i = 0; i < n; ++i) cin >> w[i] >> v[i];
cout << zeroOnePack(0, totalWeight) << endl;
}
这个程序主要是针对POJ 3624的测试数据,即使开的数组大一些也会超时的,因为对于每个物品存在选择和不选择两种情况,那么时间复杂度是O(2^n),优化的办法就是去记录已经计算过的结果,方便下次利用。
程序方面最好在《算法手写代码必备手册》的基础上来完善,解这种问题的关键是先写出**状态转移方程**,用空间复杂度不是很理想的二维数组来存储,然后根据表达式再用一位数组(也就是常说的滚动数组)来进行优化。
状态转移方程的两个关键点:写出完整的表达式,明确表达式参数的意义。
定义状态转移方程f[i][j]表示把前i个物品放进容量为j的背包所能获得的最大价值。 $$ f[i][j]=\max {f[i-1][j], f[i-1][j-w[i]]+v[i]} $$ 表达式的含义:
- 第i个装不进去,价值为f[i-1][j]
- 第i个装进去了,价值为f[i-1][j-w[i]] + v[i]。
下面的程序针对的都是POJ3624的,HDU2602和此基本上是一模一样的。
//二维数组存储的版本
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n = 3402, totalWeight = 12880;
vector<int> w(n + 1), v(n + 1);
vector<vector<int>> f(n + 1, vector<int>(totalWeight + 1));
int zeroOnePack()
{
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= totalWeight; ++j) {
f[i][j] = f[i - 1][j];
if (j >= w[i]) {
f[i][j] = max(f[i][j], f[i-1][j - w[i]] + v[i]);
}
}
}
return f[n][totalWeight];
}
int main()
{
std::ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> n >> totalWeight;
for (int i = 1; i <= n; ++i) cin >> w[i] >> v[i];
cout << zeroOnePack() << endl;
}
滚动数组优化空间复杂度¶
但是上面的程序提交会发现超过内存限制,因为开数组要N(3403)\times M(12881)的二维数组, 相比于HDU的题目数据大了很多。所以该考虑一下利用滚动数组进行优化了。 $$ f[i][j]=\max {f[i-1][j], f[i-1][j-w[i]]+v[i]} $$ 使用滚动数组的方法并不一定好理解,不妨从数学形式来看,对于f[i][j]的求解,始终需要去访问f[i-1]*,其中*代表状态转移方程中的两种形式,也就是说,二维的表示可以退化成一维的。即可以用f[j]来表示。f[j] = max(f[j], f[j - boneWeight[i]] + boneValue[i]),但是需要考虑遍历的顺序。这里是逆序遍历,保证每个物品最多使用一次,如果顺序遍历,则是完全背包问题。
解释一下为什么顺序遍历就会出现可能多次存储的情况,因为要考虑的是针对01背包这一类问题的解法,考虑**如果还是顺序遍历**,程序的核心代码是:
vector<int> d(totalWeight + 1, 0);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= totalWeight; ++j) {
d[j] = max(d[j], d[j - w[i]] + v[i]);
}
}
所以可能存在j - 2w[i]\geq0的情况,所以在计算d[j-2*w[i]]对应的值的时候,用到了d[j - w[i]]的结果,那么就意味着第i个物品被放入了2次,所以违背了题意。逆序的时候,d[totalWeight]只与d[totalWeight], d[totalWeight-w[i]] + v[i]有关,所以不会存在重复计算的过程。时间复杂度O(nW)
//滚动数组优化
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n = 3402, totalWeight = 12880;
vector<int> w(n + 1), v(n + 1);
int zeroOnePack()
{
vector<int> d(totalWeight + 1, 0);
for (int i = 1; i <= n; ++i) {
for (int j = totalWeight; j >= w[i]; --j) {
d[j] = max(d[j], d[j - w[i]] + v[i]);
}
}
return d[totalWeight];
}
int main()
{
std::ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> n >> totalWeight;
for (int i = 1; i <= n; ++i) cin >> w[i] >> v[i];
cout << zeroOnePack() << endl;
}
初始化细节¶
《背包九讲》里提到,主要针对题目要求背包恰好装满的时候的最优解。这种情况下除了d[0] = 0
,其他的都初始化为INT_MIN
,理解就是在什么都不放的情况下,只有背包容量为0的时候存在合法解,也就是0,其他部分初始化为负无穷大,代表初始时无合法解。
如果并未说背包完全填满,则初始时每个都有一个合法解,也就是什么都不装,所以就可以初始化为0.
二维01背包¶
- HDU-3496 Watch the movie(二维01背包)
- 洛谷P1855 榨取kkksc03(二维01背包)
以HDU-3496 Watch the movie
为例:共有n
个影碟,每个影碟能够增加快乐值v_i,影碟需要占用的时间是c_i,但是只能选择m个影碟,看L分钟,问如何在限制下让快乐值最大?
分析:这里存在两个限制条件,并且每个物品只有一个,相当于增加了一个维度的01背包,用d[i][j][k]
代表从前i
个物品选出j
个且总时间不超过k
的最大价值,利用滚动数组优化后,状态转移方程是:
$$
d[i][j] = \max(d[i][j], d[i - 1][j - w[i]] + value[i])
$$
//HDU-3496 Watch the movie
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n, m, volume;
vector<vector<int> > d(105, vector<int>(1005, INT_MIN));
vector<int> cost(105), value(105);
int twoDimensionZeroOnePack()
{
fill(d[0].begin(), d[0].end(), 0);
for (int i = 0; i < n; ++i) {
for (int j = m; j >= 1; --j) {
for (int k = volume; k >= cost[i]; --k) {
d[j][k] = max(d[j][k], d[j - 1][k - cost[i]] + value[i]);
}
}
}
return d[m][volume] < 0 ? 0 : d[m][volume];
}
int main()
{
std::ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int caseNum; cin >> caseNum;
while (caseNum--) {
cin >> n >> m >> volume;
for (int i = 0; i < n; ++i) {
cin >> cost[i] >> value[i];
}
cout << twoDimensionZeroOnePack() << endl;
for (int i = 0; i < n; ++i) fill(d[i].begin(), d[i].end(), INT_MIN);
}
return 0;
}
三层循环,时间复杂度是O(nmL),空间复杂度是O(mL)。
超大容量背包¶
01背包转多重背包¶
- HDU-3732 Ahui Writes Word
这种题目类型一般会出现n
和volume
很大,但是每个物品的重量要远小于volume
,根据鸽巢原理,意味着存在某一重量对应多个物品,于是转化成多重背包问题。
带负数的01背包¶
- POJ 2184 Cow Exhibition(带负数的01背包)
带浮点数的01背包¶
- HDU-1203 I need an offer(浮点数01背包)
01背包的第K大问题¶
- HDU 2639 bone collector II(01背包的第K大问题)
总结¶
在面对01背包的模型的时候,要注意观察条件,先计算时间复杂度再写代码。
- 比如经典的01背包模型,时间复杂度是O(nW)。
- 超大容量背包的情况(《挑战程序设计竞赛》),需要用折半枚举,一般题目里n比较小,时间复杂度O(n2^{n/2})。
- 表面是01背包,实际上是多重背包,比如背包容量较大(10^5),物品数量n也较大(10^5),但是重量较小(10^3),说明会存在很多重复的重量,于是变成多重背包问题,然后用队列优化。(HDU-3732 Ahui Writes Word)
- 注意题目的迷惑性,比如2020 Google kick star Round A,其实就是简单的贪心,但是题面容易诱导人用背包问题来求解,即使用单调队列优化,时间复杂度也会很大。
典型题目:
- POJ-3624 Charm Bracelet或者洛谷P2871 [USACO07DEC]手链Charm Bracelet,(洛谷的题解是可以参考的)
- HDU-2602 Bone Collector(01背包模板题)
- HDU-3732 Ahui Writes Word(01背包转型多重背包)
- ALGOSPOT-PACKING(需要输出解决方案的详细信息,输出路径的例子)
- 洛谷P1048(其实题意是没有表达出每株草只能取一次,而是从给的样例看出来的,数据也很水)
- HDU-3496 Watch the movie(二维01背包)
- POJ 1837 Balance(二维01背包)
- POJ1948:Triangular Pastures(二维01背包)
- 洛谷P1855 榨取kkksc03(二维01背包)
- POJ 2184 Cow Exhibition(带负数的01背包)
- POJ-3211 washing clothes
- HDU-2546 饭卡(需稍加思考的01背包)
- POJ-1948 Triangular Pastures
- POJ-1837 Balance
- HDU-1864 最大报销额
- HDU-1203 I need an offer(浮点数01背包)
- UVA-10130 superSale
- HDU-4182 judge's response
- POJ-2923 reclocation
- POJ-3628 bookshelf
- HDU-2955 robberies
- HDU-3466 Proud merchants
- UVA 624 CD
- HDU 2639 bone collector II(01背包的第K大问题)
- UVA 562 dividing coins
- HDU 2126 buy the souvenirs
- UVA 12563 jin ge jin qu hao
完全背包¶
题面形式:
有n种重量和价值分别为w_i,v_i的物品,每种物品数量无限,从这些物品中挑选总重量不超过W的物品,求挑选出的物品价值总和的最大值,进一步的,考虑输出价值最大时的方案(输出路径)
状态转移方程和01背包一致,经上面01背包的分析,其代码只需要正序遍历即可。时间复杂度O(nW),空间复杂度O(\max(n, W))。
vector<int> d(totalWeight + 1, 0);
for (int i = 1; i <= n; ++i) {
for (int j = w[i]; j <= totalWeight; ++j) {
d[j] = max(d[j], d[j - w[i]] + v[i]);
}
}
典型题目:
- POJ-1384或HDU-1114 Piggy-Bank
- POJ-2063 Investment
- UVA 147 dollars
- LeetCode 518.Coin Change 2(计数类型的完全背包)
- 一本通-1268:【例9.12】完全背包问题(完全背包模板题)
- UVA 357 let me count the ways
- HDU 1248 寒冰王座
- POJ 2229 Sumsets(完全背包变形)
- HDU 3127 WHU girls
- UVA 10465 Homer Simpson
- HDU 1284 铅笔兑换问题
- HDU 2159 FATE
- POJ 1252 euro efficiency
- POJ 3181 dollar dayz
- UVA 10306 e-coins
- UVA 11137 ingenuous cubrency
- UVA 674 coin change
- HDU 4508 湫湫系列故事——减肥记1
多重背包¶
基本模型:有n种物品和一个容量为v的背包,第i种物品最多有M_i件,每一件耗费的空间是C_i,价值是W_i,求如何使价值总和最大。
优化的思路是采用二进制分组来优化。时间复杂度O(W \times\sum \log c[i]),其中n
是物品数量。
二进制分组在一些数据较大的时候也会超时,所以用单调队列进行优化。
01背包思路来解决多重背包¶
每个物品c_i件,虽然共n
种物品,现在看成\sum c_i件物品,变成01背包问题来求解。时间复杂度为O(W\sum c_i),性能较差。
for (int i = 0; i < n; ++i) {
for (int j = 1; j <= c[i]; ++j) {
for (int k = m; k >= w[i]; --k) {
d[k] = max(d[k], d[k - w[i]] + v[i]);
}
}
}
return d[m];
虽然这种效率很不理想,但是还是写一个题目做个练习。
- 1269:【例9.13】庆功会
#include <bits/stdc++.h>
using namespace std;
int n, m;
vector<int> d(6005), price(505), value(505), num(505);
int multiPack()
{
for (int i = 0; i < n; ++i) {
for (int j = 1; j <= num[i]; ++j) {
for (int k = m; k >= price[i]; --k) {
d[k] = max(d[k], d[k - price[i]] + value[i]);
}
}
}
return d[m];
}
int main()
{
std::ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> n >> m;
for (int i = 0; i < n; ++i) cin >> price[i] >> value[i] >> num[i];
cout << multiPack() << endl;
return 0;
}
二进制优化的多重背包¶
- HDU 2191 买大米(多重背包模板)
时间复杂度O(W \times\sum \log c[i]),其中c_i代表每种物品的数量
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int money, riceType;
vector<int> d(101, 0);
vector<int> price(101), weight(201), num(101);
void zeroOnePack(int price, int weight)
{
for (int i = money; i >= price; --i)
d[i] = max(d[i], d[i - price] + weight);
}
void completePack(int price, int weight)
{
for (int i = price; i <= money; ++i)
d[i] = max(d[i], d[i - price] + weight);
}
void multipack(int price, int weight, int num)
{
if (price * num >= money){
completePack(price, weight);
return;
}
int k = 1;
while (k < num){
zeroOnePack(k * price, k * weight);
num -= k;
k *= 2;
}
zeroOnePack(num * price, num * weight);
}
int main()
{
int caseNum;
cin >> caseNum;
while (caseNum--){
cin >> money >> riceType;
for (int i = 1; i <= riceType; ++i){
cin >> price[i] >> weight[i] >> num[i];
}
for (int i = 1; i <= riceType; ++i)
multipack(price[i], weight[i], num[i]);
cout << d[money] << endl;
if (caseNum != 0) fill(d.begin(), d.end(), 0);
}
return 0;
}
多了一步判断,数量充足就是完全背包,不充足就是01背包。
单调队列优化多重背包¶
来自《算法竞赛进阶指南》P318。物品种类为n
,容量为W
,时间复杂度为O(nW)。
《挑战程序设计竞赛》4.4.2 双端队列的运用,代码相对《算法竞赛进阶指南》更值得参考。
- 洛谷-P1776 宝物筛选
- HDU 1171 Big Event in HDU
假设物品的种类为n
,背包容量为m
,每个物品的重量为w[i]
,价值为value[i]
,数量为num[i]
,用d[j]
代表背包容量为j
时所能得到的最大值。则:
$$
d[j] = \max_{0 \leq k \leq num[i] \&\& j - k \times w[i] \geq 0} (d[j - k\times w[i]] + value[i])
$$
在进行状态转移的时候,j
的状态只受到j - w[i], j - 2*w[i] ...
的影响,对于j-1
,其状态和j
的状态互不影响,于是我们可以根据j % w[i]
的余数进行分组,余数相同的必在一组。现在假设w[i] = 3
,那么根据余数可以分成3组:
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | \cdots |
---|---|---|---|---|---|---|---|---|---|---|
j mod w[i] |
0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 | \cdots |
余数(记为a
)相同的组成一组,然后重新编号,单独处理:
编号j |
0 | 1 | 2 | 3 | 4 | 5 | \cdots |
---|---|---|---|---|---|---|---|
对应重量 | a | a+w[i] | a+2*w[i] | a+3*w[i] | a+4*w[i] | a+5*w[i] | \cdots |
如果没有物品数量的限制,则编号为5
的可以由编号为0,1,2,3,4
状态转移过来,但是因为有数量限制的存在,那么其实相当于增加了一个长度为num[i]
的窗口,这个窗口从前往后滑动,然后求取窗口内的最大值,于是想到可以用单调队列来完成这一工作。
我们令: $$ a = j \mod w[i], b = \left[\frac{j-a}{w[i]}\right] \ j = b \times w[i] + a \ 令k^{'} = b - k; \ d\left[j - k\times w[i]\right] + value[i] = d\left[a +k^{'}\times w[i]\right] - k^{'}\times value[i] + a\times value[i] \ 0 \leq k \leq \min(\left[\frac{j-a}{w[i]}\right], num[i]) \ \max(0, b - num[i]) \leq k^{'} \leq b \ $$
//一本通-1269:【例9.13】庆功会
#include <bits/stdc++.h>
using namespace std;
vector<int> price(505), value(505), num(505);
int n, m;
vector<int> dq_pos(6005), dq_val(6005); //模拟双端队列
vector<int> d(6005);
int multiPack()
{
for (int i = 0; i < n; ++i) {
//通过余数进行分组
for (int a = 0; a < price[i]; ++a) {
int start = 0, end = 0; //双端队列的头尾
for (int j = 0; j * price[i] + a <= m; ++j) {
//if (start < end && dq_pos[start] == j - 1 - num[i]) ++start;
int tmp = d[a + j * price[i]] - j * value[i];
//保证队列单增
while (start < end && dq_val[end - 1] <= tmp) --end;
//新元素加入队列
dq_pos[end] = j;
dq_val[end++] = tmp;
//更新最大值
d[a + j * price[i]] = dq_val[start] + j * value[i];
//如果窗口长度大于num[i],删掉首部元素
if (dq_pos[start] == j - num[i]) ++start;
}
}
}
return d[m];
}
int main()
{
std::ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> n >> m;
for (int i = 0; i < n; ++i) cin >> price[i] >> value[i] >> num[i];
cout << multiPack() << endl;
return 0;
}
和LeetCode 239对比,发现29行只需要写成j - num[i]
,这个要从窗口长度k
的意义来分析,LeetCode 239中窗口长度k
是包含当前元素本身的,而我们这里的k
表示当前元素前面k
个元素转移过来,仍然是让窗口长度为1
,那么当j = 1
的时候,可以从j = 0
转移过来,由于已经处理完成,队列长度又和窗口长度相同,所以这时候需要清空队列了。
多重背包解决存在性问题¶
- POJ 1742 Coins(多重背包的存在性问题)
只需要注意初始化问题,核心程序并无太大变化。
01背包转多重背包¶
- HDU-3732 Ahui Writes Word
这种题目类型一般会出现n
和volume
很大,但是每个物品的重量要远小于volume
,根据鸽巢原理,意味着存在某一重量对应多个物品,于是转化成多重背包问题。
典型题目:
- HDU 2191 悼念512汶川大地震遇难同胞——珍惜现在,感恩生活
- HDU 3732 Ahui Writes Word
- POJ 1276 Cash machines
- HDU 2844 Coins(和POJ 1742是一个题目,但是在POJ 会TLE)(在HDU可用多重背包做)
- POJ 1742 Coins(多重背包的存在性问题)
- HDU-1171 Big Event in HDU
- HDU 1059 DIVIDING
- hdu 2844 COINS
- poj 2392 SPACE ELEVATOR
- HDU 3591 THE TROUBLE OF XIAOqian
- POJ 3260 the fewest coins
混合背包¶
实际上就是上面三种问题的结合,有的只能取一次,有的可以取无限次,有的是k次。
只需要注意多重背包的时候用单调队列优化一下。
//1270:【例9.14】混合背包
#include <bits/stdc++.h>
using namespace std;
int n, m;
vector<int> dq_val(205), dq_pos(205), d(205);
void zeroOnePack(int weight, int value)
{
for (int i = m; i >= weight; --i)
d[i] = max(d[i], d[i - weight] + value);
}
void completePack(int weight, int value)
{
for (int i = weight; i <= m; ++i)
d[i] = max(d[i], d[i - weight] + value);
}
void multiPack(int weight, int value, int num)
{
fill(dq_pos.begin(), dq_pos.end(), 0);
fill(dq_val.begin(), dq_val.end(), 0);
for (int a = 0; a < weight; ++a) {
int start = 0, end = 0;
for (int j = 0; j * weight + a <= m; ++j) {
int tmp = d[a + j * weight] - j * value;
while (start < end && dq_val[end - 1] <= tmp) --end;
dq_pos[end] = j;
dq_val[end++] = tmp;
d[j * weight + a] = dq_val[start] + j * value;
if (dq_pos[start] == j - num) ++start;
}
}
}
int main()
{
std::ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> m >> n;
int weight, value, num;
for (int i = 0; i < n; ++i) {
cin >> weight >> value >> num;
if (num == 0) completePack(weight, value);
else if (num == 1) zeroOnePack(weight, value);
else multiPack(weight, value, num);
}
cout << d[m] << endl;
return 0;
}
- HDU 1712 ACboy needs your help
- 洛谷-P1833 樱花
- 一本通-1270:【例9.14】混合背包
二维费用的背包问题¶
二维费用的背包问题是指:对于每件物品,具有两种不同的费用,选择这件物品必须同时付出这两种费用。对于每种费用都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。一般题目可能不会直接提示是二维背包,而是在可选择的物品数量上有限制,比如:HDU-3496 Watch the movie
设第i
件物品所需的两种费用分别为c_i和d_i,两种费用可付出的最大价值分别为V和U,物品的价值为w_i。
设d[i][j][k]代表前i
种物品在背包容量为j
和k
的条件下的最大价值。状态转移方程:
$$
d[i][j][k] = \max(d[i - 1][j][k], d[i-1][j - c_i][k-d_i] + w_i)
$$
然后滚动数组优化一下空间复杂度。
- 一本通-1271:【例9.15】潜水员
- HDU-3496 Watch the movie(二维01背包)
- 洛谷P1855 榨取kkksc03(二维01背包)
分组背包¶
有N件物品和一个容量为V的背包。第i
件物品的费用是w[i]
,价值是c[i]
。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
相当于把每一组看成一个物品,每个组只能每次选出一个物品,或者一个都不选,就转化成了01背包。伪代码:
for 所有的组k
for v = V ... 0
for 所有的i属于组k
f[v] = max(f[v], f[v - w[i]] + c[i])
- 一本通-1272:【例9.16】分组背包
//一本通-1272:【例9.16】分组背包
#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <algorithm>
#include <cmath>
using namespace std;
int v, n, t;
vector<int> weight(35), value(35);
vector<int> d(205);
vector<vector<int> > group(15, vector<int>(35));
int groupPack()
{
for (int i = 1; i <= t; ++i) {
for (int j = v; j >= 0; --j) {
for (int k = 1; k <= group[i][0]; ++k) {
int pos = group[i][k];
if (j >= weight[pos]) {
d[j] = max(d[j], d[j - weight[pos]] + value[pos]);
}
}
}
}
return d[v];
}
int main()
{
std::ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> v >> n >> t;
int k;
for (int i = 0; i < n; ++i) {
cin >> weight[i] >> value[i] >> k;
group[k][++group[k][0]] = i; //记录每个组的物品对应的序号
}
cout << groupPack() << endl;
return 0;
}
有依赖的背包¶
- P1064 金明的预算方案
背包问题的方案数¶
这时候物品的价值其实就是物品的数量,但是需要注意题目可能会用贪心的背景来迷惑,比如2020 Google kick start Round A的题目,注意和基础贪心问题里的“最优装载”进行区分。
- 一本通-1273:【例9.17】货币系统
- 一本通-1291:数字组合
- LeetCode 面试题8.11 硬币