前言
背包(Knapsack)问题是经典的动态规划问题,也很有实际价值。
01背包
洛谷 P2871 [USACO07DEC] Charm Bracelet S
AtCoder Educational DP Contest D – Knapsack 1
有个物品和一个总容量为 的背包。第 件物品的重量是 ,价值是 。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
这是最原始的01背包问题(即每个物品只能选
令
依次递增
在本题中,
压缩掉
此时空间复杂度为
一定要牢记这个公式,注意使用时需倒序枚举
#include <cstdio>
#define setmax(x, y) if(x < y) x = y
using namespace std;
int f[12881];
int main()
{
int n, m;
scanf("%d%d", &n, &m);
while(n--)
{
int w, v;
scanf("%d%d", &w, &v);
for(int i=m; i>=w; i--)
setmax(f[i], f[i - w] + v);
}
printf("%d\n", f[m]);
return 0;
}
01背包还有一种简单变形,即求最小剩余空间,此时用
#include <cstdio>
#define setmax(x, y) if(x < y) x = y
using namespace std;
int f[20005];
int main()
{
int n, v;
scanf("%d%d", &v, &n);
while(n--)
{
int w;
scanf("%d", &w);
for(int i=v; i>=w; i--)
setmax(f[i], f[i - w] + w);
}
printf("%d\n", v - f[v]);
return 0;
}
扩展:对付更大的
AtCoder Educational DP Contest E – Knapsack 2
本题和普通的01背包完全相同,只是数据范围改为。
注意数据范围,
其中,
这种算法的时间和空间都可以优化:
- 时间:对于每个
,循环迭代 时只需到 即可,因为当前的总价值不可能超过这个值; - 空间:用与前面完全相同的方法,压缩掉第一维空间,变成
(注意要倒序枚举 )
运用了两种优化的代码如下:
#include <cstdio>
#include <cstring>
using namespace std;
long long f[100005], t, tot, ans;
int main()
{
int n, sz;
scanf("%d%d", &n, &sz);
memset(f, 0x3f, sizeof f);
f[0] = 0;
while(n--)
{
int w, v;
scanf("%d%d", &w, &v);
tot += v;
for(int i=tot; i>=v; i--)
if((t = f[i - v] + w) < f[i] && t <= sz)
{
f[i] = t;
if(i > ans) ans = i;
}
}
printf("%lld\n", ans);
return 0;
}
完全背包
洛谷 P1616 疯狂的采药
有个物品和一个总容量为 的背包。第 件物品的重量是 ,价值是 。每个物品可以使用无限次。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
这种背包与01背包唯一的不同之处在于,每个物品使用次数不限,所以参考01背包的dp状态:
令
可以发现,实际上只需要用
对比一下01背包和完全背包的状态转移方程:
实际上,区别就在于一个是是不是很神奇?
long long
别忘了~
#include <cstdio>
#define setmax(x, y) if(x < y) x = y
using namespace std;
long long f[10000005];
int main()
{
int sz, n;
scanf("%d%d", &sz, &n);
while(n--)
{
int w, v;
scanf("%d%d", &w, &v);
for(int i=w; i<=sz; i++)
setmax(f[i], f[i - w] + v);
}
printf("%lld\n", f[sz]);
return 0;
}
多重背包
洛谷 P1776 宝物筛选
有个物品和一个总容量为 的背包。第 件物品的重量是 ,价值是 ,最多能选择 次。我们要选择一些物品,使这些物品的重量总和不超过背包容量,且价值总和最大。 。
很容易想到,可以转换成每件物品都被拆分成
,其中 ; ,其中 ; ,其中 。
这种方法的正确性这里就不详细说明了,主要依赖于二进制的拼凑。容易发现,数字
还有一种单调队列/单调栈优化,同样针对多重背包问题,时间复杂度为
#include <cstdio>
#define maxw 40004
#define setmax(x, y) if(x < y) x = y
using namespace std;
int n, w, f[maxw];
inline void add(int a, int b) // a: value, b: weight
{
for(int i=w; i>=b; i--)
setmax(f[i], f[i - b] + a);
}
int main()
{
scanf("%d%d", &n, &w);
while(n--)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
for(int i=0; (1<<i)<=c; i++)
add(a << i, b << i), c -= 1 << i;
if(c) add(a * c, b * c);
}
printf("%d\n", f[w]);
return 0;
}
混合背包
没错,就是前三种放在一起的背包。不用的物品有不同的种类。比如洛谷 P1833 樱花,就是混合背包。不过,也不要慌,直接用个分支判断,比如:
for (循环物品种类) {
if (是 0 - 1 背包)
套用 0 - 1 背包代码;
else if (是完全背包)
套用完全背包代码;
else if (是多重背包)
套用多重背包代码;
}
实际上也不一定要这样,可以全部统一成混合背包:
- 对于01背包,可选数目
。 - 对于完全背包,可选数目
。
这里就不给出详细代码,有兴趣的读者可以自己尝试一下。
总结
让我们来总结一下三种基本背包DP的异同:
项目 | 01背包 | 完全背包 | 多重背包 |
---|---|---|---|
适用场景 | 每件物品只能选择一次 | 每件物品可以无限选择 | 每件物品可以选择的次数有限 |
状态转移方程1 | 基本同01背包 | ||
时间复杂度2 | |||
空间复杂度3 | |||
编码难度 | 低 | 低 | 中 |
创作不易,如果觉得好就请给个三连,谢谢支持!
-
压缩掉第一维的
,01背包为倒序枚举 ,完全背包为正序 ↩︎ -
完全背包为优化后的复杂度,多重背包为二进制优化的复杂度。 ↩︎
-
指压缩第一维后的dp数组大小。 ↩︎
版权声明:本文为博主作者:GoodCoder666原创文章,版权归属原作者,如果侵权,请联系我们删除!
原文链接:https://blog.csdn.net/write_1m_lines/article/details/126385779