预告:我用两年写的新书《算法竞赛》,已于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=1n​mi​个物品,然后按0/1背包求解,复杂度O(C×∑i=1nmi)O(C\times\sum_{i=1}^nm_i)O(C×∑i=1n​mi​)。
   第二种思路,直接求解。定义状态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_2Xlog2​X个。题目中第iii种物品有mim_imi​个,用log2milog_2m_ilog2​mi​个数就能组合出0 ~ mim_imi​种情况。
   总复杂度从O(C×∑i=1nmi)O(C\times\sum_{i=1}^nm_i)O(C×∑i=1n​mi​)优化到了O(C×∑i=1nlog2mi)O(C\times\sum_{i=1}^nlog_2m_i)O(C×∑i=1n​log2​mi​),已经足够好了。
   注意拆分的具体实现,不能全部拆成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优化

多重背包问题和“二进制拆分”相关推荐

  1. 多重背包问题以及二进制优化

    2020.12.30开始学习AcWing算法<算法竞赛进阶指南>: 上传博客方便复习. 多重背包问题(N<= 100): //Wecccccccc //2020.12.31 #inc ...

  2. 多重背包的二进制拆分法

    在多重背包的直接拆分法中,个数为$c[i]$的物体被拆成$c[i]$种不同的物体 这样就使得物体的种类增加了很多,使得算法效率很低. 上述方法把$c[i]$拆成$c[i]$个1,于是任意选择可以表示出 ...

  3. 多重背包问题的二进制优化(C语言)

    之前一直不理解,为什么多重背包与01背包是类似的 用朴素法解决多重背包时,操作的集合分为不取i物品,取一个i物品,取两个i物品--取s个物品 所以解题的思路就是在01背包的基础上再加一重循环,将以上集 ...

  4. 动态规划 多重背包问题

    多重背包问题 I 有 N 种物品和一个容量是 V 的背包. 第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi. 求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大. ...

  5. ACwing 5. 多重背包问题 II(二进制拆分+DP)

    文章目录 1. 题目 2. 解题 1. 题目 有 N 种物品和一个容量是 V 的背包. 第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi. 求解将哪些物品装入背包,可使物品体积总和不超过 ...

  6. 洛谷 P1776:宝物筛选 ← 多重背包问题 二进制优化

    [题目来源] https://www.luogu.com.cn/problem/P1776 [题目描述] 终于,破解了千年的难题.小 F 找到了王室的宝库,里面堆满了无数价值连城的宝物. 这下小 F ...

  7. 为了OFFER,菜鸟的我必须搞懂动态规划系列三个背包问题之多重背包(二进制优化方法)

    @Author:Runsen @Date:2020/9/17 多重背包有三层循环,如果数据非常的大,那么程序就会变得非常悲伤.在多重背包的问题,其实更多的是考查多重背包的二进制优化方法.学习二进制优化 ...

  8. 九大背包问题专题--多重背包问题(二进制优化方法;单调队列问题)

    3.多重背包问题1 题目: 有N件物品和一个容量是V的背包. 第i种物品最多有si件,每件的体积是vi,价值是wi. 求解将哪些物品装入背包,可使这些物品的总体积不超过背包的容量,且价值总和最大. 输 ...

  9. AcWing 5. 多重背包问题 II(二进制优化)

    题目链接 https://www.acwing.com/problem/content/5/ 思路 part1 受了完全背包和01背包的启发我们不难想到,一种高效的方法来优化,我们讲物品的次数做一个二 ...

最新文章

  1. 企业存储管理的另一种可能 群晖如何成为NAS代名词?
  2. 几大搜索引擎的网站登录入口
  3. 数据库系统原理(第三章数据库设计 )
  4. 2018年中国视频监控行业现状及行业发展趋势分析预测【图】
  5. PXE环境下安装系统(DHCP+TFTP+HTTP+kickstart)
  6. 【Kettle】创建资源库用户
  7. Linux 基本网络配置
  8. 深入探究synchronize锁机制
  9. DevExpress TreeList GridView 样式设置
  10. linux 查看md5值
  11. 【ARM】嵌入式 ARM Linux 下移植 USB 蓝牙、交叉编译 bluez 各种版本
  12. 4G模块配置、概念、调试记录
  13. apple watch怎么改铃声
  14. SpringBoot RabbitMQ 整合使用
  15. 降噪蓝牙耳机哪款好?500元内半入耳降噪蓝牙耳机推荐
  16. 江西计算机好的本科学校,江西本科学校计算机及应用顶岗实习
  17. 无广告、无推送、无新闻,这7款手机浏览器实用且优秀
  18. 自建私有云与公有云托管对比_共享托管和云托管的比较以及如何选择
  19. 万豪国际集团旗下24家餐厅入围2022黑珍珠餐厅指南
  20. Powershell知识点1:开启脚本限制 报错:无法加载文件,因为在此系统中禁止执行脚本

热门文章

  1. c++上三角矩阵求和
  2. Charles安装及使用教程
  3. 最小公倍数的多种求法(C++代码实现)
  4. 51单片机 普中V2 超声波测距 报警 显示 基于MCS51单片机的超声波测距模块的开发
  5. 【数据结构】悬空指针和野指针
  6. Java向word表格中添加新行并赋值
  7. AndroidManifest.xml文件解析(详解)
  8. 自上而下解读ncnn系列(1):加载param模型和bin文件前向传播
  9. perl读取文件夹下的文件
  10. 笔记本无线上网设置教程(图文)