背包问题详解

背包问题

给定一组物品,每种物品都有自己的重量和价值,现有一个背包,能承受的重量有限,在受限制的重量下,去取若干物品使总价值最大。

可拆分背包问题

	有$N$件物品和一个容积为$V$的背包。每件物品具有体积$c_i$和价值$w_i$。每件物品可以**分割成任意大小**后放入背包,且单位价值体积不变。该背包中最多可以放入的物品总价值为多少?

​ 我们总是优先挑选单位体积内价值较高的物品放入背包内,所以对于每种物品,先计算其价值对体积的比值(性价比) r i = w i c i r_i=\frac{w_i}{c_i} ri=ciwi,之后对 r i r_i ri进行排序后优先选择性价比较高的物品即可。

​ 可拆分背包问题作为一种较为简单的背包,正是由于其可拆分的性质,所以能够使用贪心轻易地解决。

01背包问题

洛谷P1060 开心的金明

洛谷P1048 采药

​ 当前有 N N N件物品和一个容积为 V V V的背包。已知第i件物品的体积是 c i c_i ci,价值是 w i w_i wi。每种物品有且仅有一件,并且体积不可分割,只能选择放入或者不放入背包。现在需要选出若干件物品,在重量之和不超过V的条件下,使得总价值尽可能大。

算性价比?

反例:

c i c_i ci$w_i $性价比
482
35 5 3 \frac{5}{3} 35
34 4 3 \frac{4}{3} 34

V = 6 V=6 V=6

暴搜?

放或不放 O ( 2 n ) O(2^n) O(2n)

动态规划:

状态:前 i i i个物品,总重量不超过 j j j的前提下。所获得的最大价值为 d p [ i ] [ j ] dp[i][j] dp[i][j]

转移方程:
j < c i , d p [ i ] [ j ] = d p [ i − 1 ] [ j ] j<c_i, dp[i][j]=dp[i-1][j] j<ci,dp[i][j]=dp[i1][j]
c i ⩽ j , d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − c i ] + w i ) c_i\leqslant j, dp[i][j]=max(dp[i-1][j],dp[i][j-c_i]+w_i) cij,dp[i][j]=max(dp[i1][j],dp[i][jci]+wi)

核心代码:

for (int i = 1; i <= N; ++i) {
    for (int j = 0; j <= V; ++j) {
        if(j >= c[i]) {
            dp[i][j] = max(dp[i - 1][j - c[i]] + w[i], dp[i - 1][j]);
        } else {
            dp[i][j] = dp[i - 1][j];
        }
    }
}

优化:转移时第一维只与i-1i有关,因此我们可以将dp数组压缩

int dp[2][V];

多重背包问题

洛谷P1776 宝物筛选

​ 有 N N N种物品,第 i i i种物品的体积是 c i c_i ci,价值是 w i w_i wi,每种物品的数量都是有限的,为 n i n_i ni。现有容量为 V V V的背包,请你放入若干物品,在总体积不超过 V V V的条件下,使总价值尽可能大。

解法一:

N N N种物品逐个拆分,得到 ∑ n i \sum{n_i} ni个独立物品后用01背包解决。 O ( V ∑ n i ) O(V\sum{n_i}) O(Vni)

解法二:

在转移的过程中枚举第 i i i个物品选取的数量 k k k,和01背包的思想一样。
d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − k ∗ c i ] + k ∗ w i ) , 0 ⩽ k ⩽ n i dp[i][j]=max(dp[i][j],dp[i-1][j-k*c_i]+k*w_i),0\leqslant k\leqslant n_i dp[i][j]=max(dp[i][j],dp[i1][jkci]+kwi),0kni
复杂度 O ( V ∑ n i ) O(V\sum{n_i}) O(Vni)

核心代码:

for (int i = 1; i <= N; i++) {
    for (int j = 0; j <= V; j++) {
        for (int k = 0; k <= n[i]; k++) {
            if (j >= c[i] * k) {
                dp[i][j] = max(dp[i - 1][j - c[i] * k] + w[i] * k, dp[i][j]);
            }
        }
    }
}

这份代码和01背包相比不再有else部分了,因为k=0的时候dp[i][j]=max(dp[i-1][j],dp[i][j]),相当于01背包的else部分。

优化:

和01背包一样,由于转移方程只用到ii-1,可以转成滚动数组,但由于dp[i][j]依赖初始值,所以在每次j循环开始之前一定要

memset(dp[flag],0,sizeof(dp[flag]));

完全背包问题

	[洛谷P1616 疯狂的采药](https://www.luogu.com.cn/problem/P1616)

​ 当前有 N N N种物品,第 i i i种物品的体积是 c i c_i ci,价值是 w i w_i wi。每种物品的数量是无限的,可以任意选择若干件。现有容量为 V V V的背包,请你放入若干物品,使总体积不超过 V V V,并且总价值尽可能大。

​ 解法:虽然物品个数是无限的,但是实际上,由于背包容量有上限,每个物品最多选取的个数也是有限制的,这样可以转换成多重背包问题 n i = V c i n_i=\frac{V}{c_i} ni=ciV,进而可以转换成01背包问题。

核心代码:

for (int i = 1; i <= N; i++) {
    for (int j = 0; j <= V; j++) {
        for (int k = 0; k * c[i] <= j; k++) {
            dp[i][j] = max(dp[i - 1][j - c[i] * k] + w[i] * k, dp[i][j]);
        }
    }
}

时间效率优化:

我们可以注意到
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − c i ] + w i , d p [ i − 1 ] [ j − c i ∗ 2 ] + w i ∗ 2... ) dp[i][j]=max(dp[i-1][j],dp[i-1][j-c_i]+w_i,dp[i-1][j-c_i*2]+w_i*2...) dp[i][j]=max(dp[i1][j],dp[i1][jci]+wi,dp[i1][jci2]+wi2...)

d p [ i ] [ j − c i ] = m a x ( d p [ i − 1 ] [ j − c i ] , d p [ i − 1 ] [ j − c i ∗ 2 ] + w i , d p [ i − 1 ] [ j − c i ∗ 3 ] + w i ∗ 2... ) dp[i][j-c_i]=max(dp[i-1][j-c_i],dp[i-1][j-c_i*2]+w_i,dp[i-1][j-c_i*3]+w_i*2...) dp[i][jci]=max(dp[i1][jci],dp[i1][jci2]+wi,dp[i1][jci3]+wi2...)
也就是说,我们完全可以用dp[i][j-c[i]]的信息去更新dp[i][j],而不用多此一举去枚举k了,转移可以直接变成如下
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − c i ] + w [ i ] ) dp[i][j]=max(dp[i-1][j],dp[i][j-c_i]+w[i]) dp[i][j]=max(dp[i1][j],dp[i][jci]+w[i])

for (int i = 1; i <= n; i++) {
    for (int j = 0; j <= v; j++) {
        if (j >= c[i]) {
            dp[i][j] = max(dp[i][j - c[i]] + w[i], dp[i - 1][j]);
        } else {
            dp[i][j] = dp[i - 1][j];
        }
    }
}

背包问题的空间优化

二维转一维

以01背包为例:

原来的滚动数组代码:

int flag = 1;
for (int i = 1; i <= N; i++) {
    for (int j = 0; j <= V; j++) {
        if (j > c[i]) {
            dp[flag][j] = max(dp[1 - flag][j - c[i]] + w[i], dp[1 - flag][j]);    
        } else {
            dp[flag][j] = dp[1 - flag][j];
        }
    }
    flag = 1 - flag;
}

如果我们把 dp 数组只开成一维表示体积,然后从大到小枚举体积,也就是从 V V V 0 0 0枚举,则当前引用的 dp[j]dp[j-c[i]]仍然是计算第i-1件物品的结果,即二维状态下的dp[1-flag][j], dp[1-flag][j-c[i]],因为我们之前没有更新过dp[1-flag][j],dp[1-flag][j-c[i]]的值。

故我们可以简化转移方程:
d p [ j ] = m a x ( d p [ j − c i ] + w i , d p [ j ] ) dp[j]=max(dp[j-c_i]+w_i,dp[j]) dp[j]=max(dp[jci]+wi,dp[j])

for (int i = 1; i <= n; i++){
    for (int j = v; j >= c[i]; j--) {
        dp[j] = max(dp[j - c[i]] + w[i], dp[j]);
    }
}

同理,多重背包:

for (int i = 1; i <= N; i++) {
    for (int j = V; j >= 0; j--) {
        for (int k = 1; k <= n[i]; k++) {
            if (j >= c[i] * k) {
                dp[j] = max(dp[j - c[i] * k] + w[i] * k, dp[j]);
            }
        }
    }
}

完全背包:

for (int i = 1; i <= n; i++) {
    for (int j = c[i]; j <= v; j++) {
        dp[j] = max(dp[j - c[i]] + w[i], dp[j]);
    }
}

节省了一半空间。

扩展——分组背包问题

洛谷P1757 通天之分组背包

​ 当前有 N N N种物品,第 i i i种物品的体积是 c i c_i ci,价值是 w i w_i wi。每种物品有且仅有一件。**这些东西被分为 K K K组,同组的物品不能同时放入背包。**现有容量为 V V V的背包,请你放入若干物品,使总体积不超过 V V V,并且总价值尽可能大。

解法:

在考虑最优解时,对于每一组内的物品有两种选择策略,要么选择组中的某一个物品放入背包,要么包内的任何一个物品都不选入背包。考虑到该策略与 01 背包的相似性,我们可以将一组物品抽象成单独的物品。首先使用朴素的 01 背包写法,用 dp[i][j] 表示枚举到第 i i i 组物品时,背包体积不超过 j j j 的最大价值和。

而与 01 背包不同的是,对于组内的每一个物品需要逐一枚举。

核心代码:

for (int i = 1; i <= K; i++) {
    for (int j = 0; j <= V; j++) {
        dp[i][j] = dp[i - 1][j];
        for (int k = 0; k < n[i]; k++) {
            if (j >= c[i][k]) {
                dp[i][j] = max(dp[i][j], dp[i - 1][j - c[i][k]] + w[i][k]);
            }
        }
    }
}

总结

背包类型每种物品数量基本解法
01背包1个动规,双循环选最大值
多重背包若干个在01的基础上枚举选择的数量或将物品拆分成独立物品后按01来做
完全背包无限算出可放物品最大数量后按多重背包做
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值