当前位置: 首页 > 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/535161/

相关文章:

  • mysql的主从配置
  • 电商API接口数据采集与应用行业分析
  • AI正在淘汰的不是程序员,而是这3类人(看完你就明白了)
  • 差分曼彻斯特编码这东西挺有意思的,每个比特中间必须跳变,数据本身由比特开始处有无跳变决定。今天咱们直接撸Verilog代码,看看怎么在硬件层面实现编解码
  • B2B行业实测:矩阵跃动小陌GEO助力询盘增长180%+,AI获客转化技术拆解
  • OpenClaw+GLM-4.7-Flash:个人健康管理助手
  • 工业上位机开发实战:基于.NET 6和CIP协议,5分钟搞定与ControlLogix PLC的数据对接
  • Halcon数组分析实战:5分钟搞定极值定位与可视化(附完整代码)
  • WVP-GB28181-Pro技术深度解析:国标视频监控平台的架构演进与行业价值重塑
  • NumPy 函数手册:条件筛选与逻辑运算
  • OpenClaw的安全反思——如果你跟OpenClaw说“我讨厌我老婆”,一分钟后它告诉你“我已经把她干掉了”,你是什么心情?
  • C++开发者必看:nlohmann::json实战避坑指南(含性能优化技巧)
  • 7×24小时无人值守:矩阵跃动龙虾机器人+GEO,AI流量闭环效率实测报告
  • 解决提示词「卡壳」难题:架构师的3个创新实践破解法
  • 云原生架构设计:新手入门的核心原则
  • 5个步骤掌握TinyMaix:从环境搭建到边缘部署
  • 嵌入式系统调试技术全解析:从SRAM到SWO
  • NetMount:跨平台云存储高效管理解决方案
  • 20252912 2024-2025-2 《网络攻防实践》实验三
  • STM32F746NG按键管理库:轻量级C++状态机设计
  • InSAR处理软件与时间序列分析工具:从商业到开源的全方位指南
  • 【学术写作利器】Academic Phrasebank:从零开始掌握论文核心段落写作
  • 避开KEIL调试大坑:从printf重定向到MicroLIB选择的完整避坑指南
  • RDMA 与RoCE v2
  • Crowbar:赋能创作者的开源游戏开发效率工具
  • 嵌入式硬件脉冲计数器:高精度零丢脉冲实现原理与跨平台实践
  • MinIO桶里文件太多,list_objects卡死?试试这个‘目录管家’方案(附SpringBoot代码)
  • Java 字符串三剑客:String、StringBuilder 与 StringBuffer 深度解析与选型指南
  • 管道导波检测进阶:如何用Comsol优化裂纹识别精度(含最新信号处理方法)
  • 2026-03-25 闲话