当前位置: 首页 > news >正文

单调队列优化多重背包 详解学习笔记

背景

考虑如下的背包问题:

给定 \(n\) 种物品和一个背包,第 \(i\) 种物品的体积为 \(c_i\),价值为 \(w_i\),并且有 \(m_i\) 个。背包的总容量为 \(C\) 。设计一种装物品的方法,使装入背包的物品总价值最大。

考虑朴素的做法。我们枚举每个物品,枚举背包容量,再枚举每种物品选了几件。使用滚动数组优化后,我们写出如下的转移方程:

\[dp_j = \max\{dp_{j-kc_i}+kw_i\},\ 1\le k\le \min\{m_i,\frac{j}{c_i}\} \]

使用 \(i,j,k\) 三层循环,复杂度为 \(O(n^3)\)

一种有效的优化是二进制拆分优化。通过把 \(m_i\) 个物品拆分成 \(\log m_i\) 个物品组,每个物品组看成一个新物品,并使用 0/1 背包求解。但是,这并非最优的解法。

事实上。我们有 \(O(nC)\) 的做法,即单调队列优化。

思路

首先我们知道,单调队列优化适用于涉及 \(i,j\) 的状态转移方程,且 \(j\) 被限制在一个与 \(i\) 有关的滑动窗口内,随着 \(i\) 的增长,不同的 \(i\) 对应的窗口范围有重叠。本题中,\(i,j\) 没有什么关系,不能优化。我们考虑优化 \(j,k\) 。此时,我们把 \(i\) 看作定值。

为了应用单调队列,我们需要找到 \(k\)\(j\) 上的滑动窗口。我们发现,\(dp_{j}\) 只会由 \(dp_{j-kc_i},\ dp_{j-(k-1)c_i},\ dp_{j-(k-2)c_i},\ \ldots\) 转移而来,\(dp_{j+c_i}\) 只会由 \(dp_{j},\ dp_{j-kc_i},\ dp_{j-(k-1)c_i},\ \ldots\) 转移而来。这里,窗口发生了重叠。而这些依赖项又是 \(\bmod c_i\) 同余的。也就是说,为了产生滑动的窗口,我们不妨把外层枚举 \(j\) 改为枚举 \(j\bmod c_i\) 的余数 \(b\),这样,对于每个 \(b\),决策点 \(k\) 都被限制在了一个有重叠的滑动窗口中,且窗口长度为 \(\min\{m_i,\frac{j}{c_i}\}\)。 我们就可以使用单调队列优化。

下面,我们实现上面的思路。我们把有关 \(j,k\) 的状态转移方程改为有关 \(b\) 的形式。为此,我们定义如下变量:

\(j=b+yc_i\),其中,\(b=j\bmod c_i\)\(y=\lfloor\frac{j}{c_i} \rfloor\),带入原方程,我们有:

\[dp_{b+yc_i} = \max\{dp_{b+(y-k)c_i}+kw_i\},\ 1\le k\le \min\{m_i,\frac{j}{c_i}\} \]

再令 \(x = y-k\),我们有:

\[dp_{b+yc_i} = \max\{dp_{b+xc_i}-xw_i+yw_i\},\ y-\min\{m_i,\frac{j}{c_i}\}\le x\le y \]

\(yw_i\) 提出来,然后我们就可以用单调队列维护 \(\max\{dp_{b+xc_i}-xw_i\}\),从而把复杂度降低到 \(O(nC)\)

例题

Luogu P1176 宝物筛选

这是一道模板题。可以对照代码进一步理解上文的推导。

#include<iostream>
#define fastio ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define MIKU 0
using namespace std;int n, C;
int q[100005], hd, tl;
int dp[100005], tmp[100005];  //用于维护 dp[b+y*c_i]-y*w_iint main() {fastio;cin>>n>>C;for(int i=1; i<=n; i++) {int w, c, m; cin>>w>>c>>m;if(m > C / c) m = C / c;  //确定窗口长度,即每个物品最多取多少for(int b=0; b<c; b++) {  //按余数枚举hd = tl = 0;for(int y=0; y<=(C-b)/c; y++) {  //j<=C,故 y<=j-b/c=(C-b)/ctmp[y] = dp[b+y*c] - y*w;  //单调队列维护这个值while(hd < tl && tmp[q[tl-1]] <= tmp[y]) tl --;  //单调递减,使队头最大q[tl++] = y;while(hd < tl && y - q[hd] > m) hd ++;  //超出窗口丢掉dp[b+y*c] = max(dp[b+y*c], tmp[q[hd]]+y*w);  //dp决策,与转移方程是相同的}}}cout<<dp[C];return MIKU;
}

Luogu P3423 [POI 2005] BAN-Bank Notes

这题考查的是方案设计。

题意

给定一些面值的硬币,共 \(n\) 种,每种硬币数量有限。求凑出 \(k\) 金额所需的最少硬币数,并输出一种方案。

思路

这题数据范围不大,但是下面还是提供一种单调队列优化的解法以巩固理解。

第一问就是一个裸的多重背包。我们把面值看作体积,价值恒为 \(1\) ,则问题转化成装满容量为 \(k\) 的背包得到的最小价值。

主要问题在于第二问。我们不妨定义 \(g_{i,j}\) 为考虑前 \(i\) 种硬币,恰好凑出 \(j\) 面额时,\(i\) 硬币选了多少枚。则当满足转移条件时,即 tmp[q[hd]]+y < dp[b+y*c] ,我们不仅进行状态的转移,还记录 \(g_{i,b+yc_i}=y-q_{hd}\) 。这里隐藏的原理是:进行状态转移时,所选的物品个数就是当前的窗口长度。如果你能自己想到这一点,说明你已经基本理解了单调队列优化多重背包的原理。

最后输出时,我们只需要从终状态按照 g[][] 逆推回每一步的决策,用一个栈来存储,最后输出栈内元素即可。

代码

#include<iostream>
#include<cstring>
#define fastio ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define MIKU 0
using namespace std;int n, C;
int val[20005], cnt[20005];
int dp[20005], g[205][20005], tmp[20005];
int q[20005], hd, tl;
int stk[20005], top;int main() {fastio;memset(dp, 0x3f, sizeof(dp)); dp[0] = 0;cin>>n;for(int i=1; i<=n; i++) cin>>val[i];for(int i=1; i<=n; i++) cin>>cnt[i];cin>>C;for(int i=1; i<=n; i++) {int c = val[i], m = cnt[i];if(m > C/c) m = C / c;for(int b=0; b<c; b++) {hd = tl = 0;for(int y=0; y<=(C-b)/c; y++) {tmp[y] = dp[b+y*c] - y;  //本来是 -y*w,但 w=1,省略。后文同。while(hd < tl && tmp[q[tl-1]] >= tmp[y]) tl --;q[tl++] = y;while(hd < tl && y - q[hd] > m) hd ++;if(tmp[q[hd]]+y < dp[b+y*c]) {dp[b+y*c] = tmp[q[hd]] + y;g[i][b+y*c] = y - q[hd];}}}}cout<<dp[C]<<'\n';for(int i=n, rst = C; i; i--) {stk[++top] = g[i][rst];rst -= g[i][rst] * val[i];}while(top) cout<<stk[top--]<<' ';return MIKU;
}
http://www.jsqmd.com/news/535121/

相关文章:

  • Llama-3.2V-11B-cot实战教程:Streamlit界面响应延迟优化与调试
  • 手把手教你用JavaScript实现炉石酒馆战棋战斗模拟器(附GitHub源码)
  • 关于生成器中yield“怪异”用法的理解
  • 从堆叠注入到系统提权:一次BC站点的完整渗透测试剖析
  • 5个实用方法解决Armbian系统版本管理难题:从识别到升级的完整指南
  • OpenCore Legacy Patcher终极指南:从故障排除到高级配置优化
  • yuzu模拟器终极性能优化:突破帧率限制的完整指南
  • 从COCO到你的业务:如何为自定义数据集定义‘小目标’?聊聊mAP_s背后的评估陷阱与调优实战
  • 嵌入式工程师必看:如何用查表法在无FPU的MCU上快速计算log10
  • 2026.3.25
  • Wan2.2-I2V-A14B部署教程:Windows WSL2环境下RTX4090D驱动适配方案
  • 边缘AI语音交互平台:xiaozhi-esp32开源项目深度解析
  • SDMatte镜像国产化适配:昇腾/海光平台移植可行性评估
  • S2-Pro Java开发实战:集成JDK1.8与SpringBoot的微服务智能日志分析
  • 虚拟角色驱动引擎:如何让数字形象拥有生命?
  • 墨语灵犀文史修习实战:《The Analects》英译本→古风中文回译对照生成
  • Java程序员如何借力AI突围:从CRUD到智能开发的转型指南
  • 5分钟快速上手Ultralytics YOLO:目标检测的终极解决方案
  • 车载SerDes技术实战:从摄像头到ECU的数据传输避坑指南
  • SIM800L GSM模块实战:从串口调试到短信收发的完整避坑指南
  • 轻量化录屏工具:基于ScreenCapture Kit重新定义macOS录制体验
  • LTspice DC Sweep双变量扫描实操:三极管输出特性曲线与厄利电压的仿真观测指南
  • 香橙派系统镜像选错了怎么办?手把手教你降级回退到稳定版本(以3.0.6为例)
  • 将普通USB摄像头变身高清网络摄像头的终极指南
  • 手把手教你用可控硅DIY光控小夜灯(附完整电路图)
  • IDEA开发者必备:利用SFTP实现本地代码与远程服务器实时同步的技巧
  • openclaw服务器配置
  • 终极浏览器AI助手:5分钟实现自动化网页操作与智能研究
  • COMSOL激光双点烧蚀铝合金的固体传热与变形几何全解:动态操作+视频教程
  • 基于飞牛NAS与Docker的Dify私有化部署实战指南