学习自 https://www.bilibili.com/video/av70148899?p=3 ,讲得很清晰
背包问题
给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高
一丶0-1背包
我们有
种物品,物品
的重量为
,价值为
,我们假定所有物品的重量和价值都是非负的。如果限定每种物品只能选择0个或1个,求背包所能承受的最大重量为
。
空间优化:
上面的空间复杂度是O(nW),假如我们只需要用最后的结果,那么我们可以将空间复杂度压缩到O(n),因为01背包的问题可以正确更新依赖于上一行
和
号
wi之前肯定没有更新,因为这段是没有必要去更新的
深绿色的之所以安全:因为wi之前的没有被更新是上一行的内容;
红色的不安全:因为用到的深绿色的内容是本行的内容,所以一旦深绿色的出现变换,那么红色就会更新错误
解决方案:从右到左更新
const int Wsize = 5005;
int dp[Wsize];
int W; //背包总承受重量
int v[M]; int w[M]; //价值,重量
void init() {
memset(dp, 0, sizeof(dp));
}
void zero_one_bag(int vi,int wi) {
for (int i = W; i >= wi; i--)
dp[i] = max(dp[i], dp[i - wi] + vi);
}
int solve(int n) { //n是物品数量
for (int i = 0; i < n; i++) {
zero_one_bag(v[i], w[i]);
}
return dp[W];
}
放满情况:
初始化: memset(dp, -1, sizeof(dp));,-1代表这种方案不可行
且dp[0]=0;
二丶完全背包
我们有
种物品,物品
的重量为
,价值为
,我们假定所有物品的重量和价值都是非负的。如果限定每种物品只能选择无限个,求背包所能承受的最大重量为
。
上面的思路是将完全背包看成01背包,思路就是每个商品拥有不能再装的数量,比如1号商品拥有3个,2号商品拥有2个,最后变为5个商品的01背包问题,但我们没有用到他们是同一件商品的特性, 一旦我们用了这个特性,直接从左往右更新,那么我们可以让时间复杂度降低,避免了同个物品的循环
const int Wsize = 5005;
int dp[Wsize];
int W; //背包总承受重量
int v[M]; int w[M]; //价值,重量
void init() {
memset(dp, 0, sizeof(dp));
}
void complete_bag(int vi, int wi) {
for (int i =wi; i <= W; i--)
dp[i] = max(dp[i], dp[i - wi] + vi);
}
int solve(int n) { //n是物品数量
for (int i = 0; i < n; i++) {
complete_bag(v[i], w[i]);
}
return dp[W];
}
三丶多重背包
我们有 种物品,物品 的重量为 ,价值为 ,我们假定所有物品的重量和价值都是非负的。如果限定每种物品只能选择有限个,求背包所能承受的最大重量为 。
下面是位运算来写
比如
,那么就可以转化为01背包,不过1 ,2 ,4 ,6都要化为整体,乘以参数vi * k, wi * k
证明需要去看看数学的了
const int Wsize = 5005;
int dp[Wsize];
int W; //背包总承受重量
int v[M]; int w[M]; int c[M]; //价值,重量,数量
void init() {
memset(dp, 0, sizeof(dp));
}
void muti_bag(int vi, int wi, int ci) {
//数量足够,直接完全背包做,也可以不必要写
if (W / wi <= ci) {
complete_bag(vi, wi);
return;
}
// 1~13 :1 2 4 + 6
for (int i = 1; i <= ci; i <<= 1) {
ci -= i;
zero_one_bag(vi * i, wi * i);
}
zero_one_bag(vi * ci, wi * ci);
}
int solve(int n) { //n是物品数量
for (int i = 0; i < n; i++) {
muti_bag(v[i], w[i], c[i]);
}
return dp[W];
}