多重背包问题和“二进制拆分”
预告:我用两年写的新书《算法竞赛》,已于2022年2月交给清华大学出版社,预计于2022年7月出版。《算法竞赛》是一本“大全”,内容覆盖“基础-中级-高级”,篇幅700页左右。部分知识点的草稿已经在本博客发表。本篇博客节选自新书《算法竞赛》的“5.2 经典线性DP问题”。
文章目录
- 1、多重背包问题的简单DP解法
- 2、用“二进制拆分”优化求解多重背包
- 3、用单调队列优化解多重背包
多重背包问题:给定nnn种物品和一个背包,第iii种物品的体积是cic_ici,价值为wiw_iwi,并且有mim_imi个,背包的总容量为CCC。如何选择装入背包的物品,使得装入背包中的物品的总价值最大?
这是一个经典的DP问题,是0/1背包问题的扩展。具体描述见下面的例题。
宝物筛选 洛谷 P1776 https://www.luogu.com.cn/problem/P1776
输入:第一行是整数 nnn和CCC,分别表示物品种数和背包的最大容量。
接下来 nnn 行,每行三个整数 wi、ci、miw_i、c_i、m_iwi、ci、mi,分别表示第iii个物品的价值、体积、数量。
输出:输出一个整数,表示背包不超载的情况下装入物品的最大价值。
这里给出3种DP解法。推荐第2种解法,它把多重背包转化为:“二进制优化+0/1背包”。
1、多重背包问题的简单DP解法
有两种思路。
第一种思路,转换为0/1背包问题。把相同的mim_imi个第iii种物品看成独立的mim_imi个,总共∑i=1nmi\sum_{i=1}^nm_i∑i=1nmi个物品,然后按0/1背包求解,复杂度O(C×∑i=1nmi)O(C\times\sum_{i=1}^nm_i)O(C×∑i=1nmi)。
第二种思路,直接求解。定义状态dp[i][j]dp[i][j]dp[i][j]:表示把前iii个物品装进容量jjj的背包,能装进背包的最大价值。第iii个物品分为装或不装两种情况,得到多重背包的状态转移方程:
dp[i][j] = max{dp[i-1][j], dp[i-1][j-k*c[i]] + k*w[i]}
1 ≤ k ≤ min{m[i], j/c[i]}
代码直接写i、j、ki、j、ki、j、k三重循环,复杂度和第一种思路的复杂度一样。下面用滚动数组编码,提交判题后会超时。
//洛谷 P1776:滚动数组版本的多重背包(超时TLE)
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int n,C,dp[N];
int w[N],c[N],m[N]; //物品i的价值、体积、数量
int main(){cin >> n >> C; //物品数量,背包容量for(int i=1;i<=n;i++) cin>>w[i]>>c[i]>>m[i];
//以下是滚动数组版本的多重背包for(int i=1;i<=n;i++) //枚举物品for(int j=C;j>=c[i];j--) //枚举背包容量for(int k=1; k<=m[i] && k*c[i]<=j; k++) dp[j] = max(dp[j],dp[j-k*c[i]]+k*w[i]);cout << dp[C] << endl;return 0;
}
2、用“二进制拆分”优化求解多重背包
这是一种简单而有效的技巧。在解法111的基础上加上这个优化,能显著改善复杂度。
原理很简单,例如第iii种物品有mim_imi = 25个,这25个物品放进背包的组合,有0 ~ 25的26种情况。不过要组合成26种情况,其实并不需要25个物品,5个就够了。根据二进制的计算原理,任何一个十进制整数XXX,都可以用1、2、4、8、…这些2的倍数相加得到,例如25 = 16 + 8 + 1,这些2的倍数只有log2Xlog_2Xlog2X个。题目中第iii种物品有mim_imi个,用log2milog_2m_ilog2mi个数就能组合出0 ~ mim_imi种情况。
总复杂度从O(C×∑i=1nmi)O(C\times\sum_{i=1}^nm_i)O(C×∑i=1nmi)优化到了O(C×∑i=1nlog2mi)O(C\times\sum_{i=1}^nlog_2m_i)O(C×∑i=1nlog2mi),已经足够好了。
注意拆分的具体实现,不能全部拆成2的倍数,而是先按2的倍数从小到大拆,最后是一个小于等于最大倍数的余数。
对mim_imi这样拆分非常有必要,能够保证拆出的数相加在[1, mim_imi]范围内,不会大于mim_imi。例如mim_imi = 25,把它拆成1、2、4、8、10,最后是余数10,10 < 16 = 24,读者可以验证用这5个数能组合成1~25内的所有数字,不会超过25。如果把25拆成1、2、4、8、16,相加的范围就是[1, 31]了。
二进制优化之后,这个问题就变成了一个正常的0/1背包问题。所以,多重背包的解法是:二进制优化 + 0/1背包。
//洛谷 P1776:二进制拆分+滚动数组
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int n,C,dp[N];
int w[N],c[N],m[N];
int new_n; //二进制拆分后的新物品总数量
int new_w[N],new_c[N],new_m[N]; //二进制拆分后新物品
int main(){cin >> n >>C; for(int i=1;i<=n;i++) cin>>w[i]>>c[i]>>m[i];
//以下是二进制拆分int new_n = 0;for(int i=1;i<=n;i++){for(int j=1;j<=m[i];j<<=1) { //二进制枚举:1,2,4...m[i]-=j; //减去已拆分的new_c[++new_n] = j*c[i]; //新物品new_w[new_n] = j*w[i]; }if(m[i]){ //最后一个是余数new_c[++new_n] = m[i]*c[i];new_w[new_n] = m[i]*w[i]; }}
//以下是滚动数组版本的0/1背包for(int i=1;i<=new_n;i++) //枚举物品for(int j=C;j>=new_c[i];j--) //枚举背包容量dp[j] = max(dp[j],dp[j-new_c[i]]+new_w[i]);cout << dp[C] << endl;return 0;
}
解法2可以看作多重背包问题的标准解法,不过,还有更优的解法3。
3、用单调队列优化解多重背包
这种方法的复杂度为O(nC)O(nC)O(nC),是最优的解法。
DP的单调队列优化比较复杂,蓝桥杯省赛估计用不着。
详情见将出版的《算法竞赛》“5.8 单调队列优化”。如果想提前了解,这部分已经发表在博客里:单调队列DP优化
多重背包问题和“二进制拆分”相关推荐
- 多重背包问题以及二进制优化
2020.12.30开始学习AcWing算法<算法竞赛进阶指南>: 上传博客方便复习. 多重背包问题(N<= 100): //Wecccccccc //2020.12.31 #inc ...
- 多重背包的二进制拆分法
在多重背包的直接拆分法中,个数为$c[i]$的物体被拆成$c[i]$种不同的物体 这样就使得物体的种类增加了很多,使得算法效率很低. 上述方法把$c[i]$拆成$c[i]$个1,于是任意选择可以表示出 ...
- 多重背包问题的二进制优化(C语言)
之前一直不理解,为什么多重背包与01背包是类似的 用朴素法解决多重背包时,操作的集合分为不取i物品,取一个i物品,取两个i物品--取s个物品 所以解题的思路就是在01背包的基础上再加一重循环,将以上集 ...
- 动态规划 多重背包问题
多重背包问题 I 有 N 种物品和一个容量是 V 的背包. 第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi. 求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大. ...
- ACwing 5. 多重背包问题 II(二进制拆分+DP)
文章目录 1. 题目 2. 解题 1. 题目 有 N 种物品和一个容量是 V 的背包. 第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi. 求解将哪些物品装入背包,可使物品体积总和不超过 ...
- 洛谷 P1776:宝物筛选 ← 多重背包问题 二进制优化
[题目来源] https://www.luogu.com.cn/problem/P1776 [题目描述] 终于,破解了千年的难题.小 F 找到了王室的宝库,里面堆满了无数价值连城的宝物. 这下小 F ...
- 为了OFFER,菜鸟的我必须搞懂动态规划系列三个背包问题之多重背包(二进制优化方法)
@Author:Runsen @Date:2020/9/17 多重背包有三层循环,如果数据非常的大,那么程序就会变得非常悲伤.在多重背包的问题,其实更多的是考查多重背包的二进制优化方法.学习二进制优化 ...
- 九大背包问题专题--多重背包问题(二进制优化方法;单调队列问题)
3.多重背包问题1 题目: 有N件物品和一个容量是V的背包. 第i种物品最多有si件,每件的体积是vi,价值是wi. 求解将哪些物品装入背包,可使这些物品的总体积不超过背包的容量,且价值总和最大. 输 ...
- AcWing 5. 多重背包问题 II(二进制优化)
题目链接 https://www.acwing.com/problem/content/5/ 思路 part1 受了完全背包和01背包的启发我们不难想到,一种高效的方法来优化,我们讲物品的次数做一个二 ...
最新文章
- 企业存储管理的另一种可能 群晖如何成为NAS代名词?
- 几大搜索引擎的网站登录入口
- 数据库系统原理(第三章数据库设计 )
- 2018年中国视频监控行业现状及行业发展趋势分析预测【图】
- PXE环境下安装系统(DHCP+TFTP+HTTP+kickstart)
- 【Kettle】创建资源库用户
- Linux 基本网络配置
- 深入探究synchronize锁机制
- DevExpress TreeList GridView 样式设置
- linux 查看md5值
- 【ARM】嵌入式 ARM Linux 下移植 USB 蓝牙、交叉编译 bluez 各种版本
- 4G模块配置、概念、调试记录
- apple watch怎么改铃声
- SpringBoot RabbitMQ 整合使用
- 降噪蓝牙耳机哪款好?500元内半入耳降噪蓝牙耳机推荐
- 江西计算机好的本科学校,江西本科学校计算机及应用顶岗实习
- 无广告、无推送、无新闻,这7款手机浏览器实用且优秀
- 自建私有云与公有云托管对比_共享托管和云托管的比较以及如何选择
- 万豪国际集团旗下24家餐厅入围2022黑珍珠餐厅指南
- Powershell知识点1:开启脚本限制 报错:无法加载文件,因为在此系统中禁止执行脚本