题目链接
前置知识
01背包 & 完全背包
三类背包问题的对比:
- 01背包:物品只能选择一次
- 完全背包: 物品选择无限制次数
- 多重背包: 物品只能选择k次
多重背包是01背包的变形,抽象来看,01背包就是物品只能选择1次的多重背包
那实现多重背包其实就是在01背包的代码中再加入一层循环,遍历k个物品的选择情况
简单实现
背包容量n,物品数量m, 物品重量w数组, 价值v数组,数量c数组
vector<int>f(n+1);
for(int i=0;i < m; i++){
for(int j = n; j >= w[i]; j--){
for(int k = 1; k <= c[i] && k*w[i] <= j; k++ ){ //数量小于c[i],k个物品的重量不能超过当前的容量j
f[j] = max(f[j], f[j-k*w[i]] + k * v[i]); //取原本的价值和选择k个物品i的最大值
}
}
}
OK, 三种背包就圆满落幕了,主要是理解思路,看不懂的请在评论区痛批(还是轻点)
回归本题
解题思路
- 简化题意:
Q: 货船的种类数量。V: 李华可用的总预算(单位:元)。ships: 一个列表,其中每个元素是一个元组[m[i], v[i], w[i]],分别表示第i种货船的数量、租赁价格和每艘货船的最大载货量。
(题意挺简洁的,不需要简化了)
几乎就是多重背包问题的模板了
把V类比为容量,Q类比成物品数量,ships[i]数组的三元组分别对应物品的数量,重量和价值
那么代码其实就是上面的代码,换一下变量就可以了
代码
def solution(Q, V, ships):
dp = [0] * (V + 1)
for ship in ships:
m, v, w = ship[0], ship[1], ship[2]
for j in range(V, v - 1, -1):
for k in range(1, m + 1):
if j >= v * k:
dp[j] = max(dp[j], dp[j - v * k] + w * k)
return dp[V]
优化[选读]
通过二进制分组优化多重背包
我们考虑朴素多重背包的时间复杂度, 大概是
其实部分我们做不出啥优化,唯一能优化的就是最后一个
思想是把一个大数c转换成多位二进制来计算
比如c=5, 我们朴素做法会从1-5进行遍历
我们其实可以转换一下, 5 可以由 1 + 2 + 2得到
我们把物品数量拆出来, 拆成
此时的list[i]代表直接选取i个物品,不需要在依次遍历了
模拟下这个过程 , 假设一个物品 v = 2 , w = 2, c = 5, 容量5 口算一下答案为5(4)
朴素算法需要 1 * 4 * 5 = 20次(大概,反正十多次)
-
当前为list[1] = [2,2]
- c = 5, 选择, f[5] = 2 + f[3] = 2
- c = 4, 选择, f[4] = 2 + f[2] = 2
- c = 3, 选择, f[3] = 2 + f[1] = 2
- c = 2, 选择, f[2] = 2 + f[0] = 2
- c = 1, 不能选择, f[1] 不变
-
当前为list[2] = [4,4]
- c = 5, 选择, f[5] = 4 + f[1] = 4
- c = 4, 选择, f[4] = 4 + f[0] = 4
- c = 3, 不能选择,f[3]不变
- c = 2, 不能选择,f[2]不变
- c = 1, 不能选择,f[1]不变
-
当前为list[3] = [4,4] 与上一轮一样
明显可以看出二进制的情况下会比遍历少很多次重复计算, 数字越大越明显
特殊的 c= 10 , 拆成 1 + 2 + 4 + 3, 会出现一个非的数字,我们在最后进行处理即可
// 拆分代码
int v = 2, w = 2, c = 5;
vector<pair<int,int>>list;
int k = 1;
while(c > k) {
list.push_back({w *k, v * k});
c -= k;
k <<= 1; //k *= 2
}
if(c > 0) list.push_back({w *c, v * c});
拆分后,所有物品取n次都存在于了list数组中, 我们只需要跑01背包就行,此时list数组就是所有物品
具体代码,以本题为例
int solution(int Q, int V, vector<vector<int>> ships) {
vector<pair<int, int>> list;
for (int i = 0; i < Q; i++) {
int c = ships[i][0];
int w = ships[i][1];
int v = ships[i][2];
int k = 1;
while (c > k) {
list.push_back({w * k, v * k});
c -= k;
k <<= 1;
}
// 如果剩下的物品个数大于0,加入到列表
if (c > 0)list.push_back({w * c, v * c});
}
vector<int> f(V + 1, 0);
for (int i = 0; i < list.size(); i++) {
for (int j = V; j >= list[i].first; j--) { // 从大到小更新
f[j] = max(f[j], f[j - list[i].first] + list[i].second);
}
}
return f[V];
}