动态规划DP 背包问题 多重背包问题(朴素版+二进制优化+单调队列)
概览检索
动态规划DP 概览(点击链接跳转)
动态规划DP 背包问题 概览(点击链接跳转)
多重背包问题1
原题链接
AcWiing 4. 多重背包问题1
题目描述
有 N种物品和一个容量是 V的背包。
第 i 种物品最多有 si件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
题目分析
多重背包:对于每个物品可以选择0,1,2,···,s[i]个(有限个)。
闫氏DP分析法

f [ i , j ] f[i,j] f[i,j] 表示从1~i 个物品中选择且总体积不超过 j 的选法中价值最大的那个。
对于第i个物品,
可以选0个 i,即在 0~i -1 个物品中选择,体积不超过j,即 f [ i − 1 , j ] f[i-1,j] f[i−1,j]。
可以选1个 i,同时在 0~i -1 个物品中选择,体积不超过 j − v [ i ] j-v[i] j−v[i],即 f [ i − 1 , j − v [ i ] ] + w [ i ] f[i-1,j-v[i]]+w[i] f[i−1,j−v[i]]+w[i]。
···
可以选 s[i] 个 i,同时在 0~i -1 个物品中选择,体积不超过 j − s [ i ] × v [ i ] j-s[i] \times v[i] j−s[i]×v[i],即 f [ i − 1 , j − s [ i ] × v [ i ] ] + s [ i ] × w [ i ] f[i-1,j-s[i] \times v[i]]+s[i] \times w[i] f[i−1,j−s[i]×v[i]]+s[i]×w[i]。
其中,选择物品i 的个数由限定有限的s[i]和背包体积共同限定。
综上,
f [ i , j ] f[i,j] f[i,j] 即为上述所有结果的最大值。
f [ i , j ] = m a x ( f [ i − 1 , j ] , f [ i − 1 , j − k × v [ i ] ] + k × w [ i ] ) f[i,j]=max(f[i-1,j],f[i-1,j-k \times v[i]]+k \times w[i]) f[i,j]=max(f[i−1,j],f[i−1,j−k×v[i]]+k×w[i])
完整代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N=110;
int n,m;
int v[N],w[N],s[N];
int f[N][N];
int main(){cin>>n>>m;for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];//遍历n个物品for(int i=1;i<=n;i++){//遍历背包体积for(int j=0;j<=m;j++){//遍历物品i的个数for(int k=0;k<=j/v[i]&&k<=s[i];k++){f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);}}}cout<<f[n][m]<<endl;return 0;
}
多重背包问题2
原题链接
AcWing 5.多重背包问题2
题目分析
与上述完全背包1的描述完全相同,不同点在于数据范围的变化。
数据范围
0<N≤1000
0<V≤2000
0<vi,wi,si≤2000
数据范围变大,由原来的100变为1000/2000,需对原先代码进行优化,采用 二进制优化。
在原先的代码中,我们枚举了0~s[i]的所有选法,
是否存在其他的办法让我们不用一项一项枚举呢?
采用 打包 。
情况1:s[i]恰为 2 k − 1 2^{k}-1 2k−1
例如,s[i]=127,
原来我们需枚举:1,2,3,···,127,127,共s=127个。
优化后,我们采用打包,打包为1个,2个,4个,8个,···,64个(2的次方),各位一组,共7组。
这相当于7组物品,每组物品可以选择 用 or 不用,相当于转化为 01背包问题 。
复杂度由 s s s 转化为 log s \log_{}s logs
而这些打包数能否组合表示从1~127的所有数呢?
显然是可以的,
1,2可以组合成1~3的任意数;
1,2,4可以组合成1~7的任意数;
1,2,4,8可以组合成1~15的任意数;
···
1,2,4,···,64恰好可以组合成1~127的任意数。
因此,上述打包数可以组合表示1~127的所有数。
示意图如下:

情况2:s[i]不为 2 k − 1 2^{k}-1 2k−1
例如,s[i]=120,
原来我们需枚举:1,2,3,···,64,···,120,共s=120个。
优化后,我们采用打包,打包为1个,2个,4个,8个,···,32个, ( 120 − 1 − 2 − 4 − ⋅ ⋅ ⋅ − 32 ) = 57 (120-1-2-4-···-32)=57 (120−1−2−4−⋅⋅⋅−32)=57个,各为一组,共7组。
这相当于7组物品,每组物品可以选择 用 or 不用,相当于转化为 01背包问题 。
复杂度由 s s s 转化为 log s \log_{}s logs
而这些打包数能否组合表示从1~120的所有数呢?
显然是可以的,
1,2可以组合成1~3的任意数;
1,2,4可以组合成1~7的任意数;
···
1,2,4,···,32恰好可以组合成1~63的任意数。
加上57,则可以组合成1+57 ~ 63+57即 58 ~ 120 的任意数。
拼接起来,
因此,上述打包数可以组合表示1~120的所有数。
综上,
对于任意的s,
打包为 1 , 2 , 4 , 8 , ⋅ ⋅ ⋅ , 2 k , c 1,2,4,8,···,2^{k},c 1,2,4,8,⋅⋅⋅,2k,c ,其中 c < 2 k + 1 c<2^{k+1} c<2k+1 ,
1 , 2 , 4 , 8 , ⋅ ⋅ ⋅ , 2 k 1,2,4,8,···,2^{k} 1,2,4,8,⋅⋅⋅,2k可以凑出 0 ∼ 2 k + 1 − 1 0 \thicksim2^{k+1}-1 0∼2k+1−1 的所有数,
加上c,则可以凑出 c ∼ 2 k + 1 − 1 + c c\thicksim2^{k+1}-1+c c∼2k+1−1+c 的所有数,
又由于 c < 2 k + 1 c<2^{k+1} c<2k+1 ,
即凑出了 0 ∼ 2 k + 1 − 1 + c 0 \thicksim2^{k+1}-1+c 0∼2k+1−1+c 的所有数,
使 2 k + 1 − 1 + c = s 2^{k+1}-1+c=s 2k+1−1+c=s 即可凑出 0 ∼ s 0 \thicksim s 0∼s 的所有数。
自此打包为 log s \log_{}s logs 组物品,转化为 01 背包问题。
完整代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N=20000,M=2010;
int v[N],w[N];
int f[N];
int n,m;
int main(){cin>>n>>m;int cnt=0;for(int i=1;i<=n;i++){int a,b,s;cin>>a>>b>>s; //体积,价值,数量int k=1;//2^k的打包操作while(k<=s){cnt++;v[cnt]=k*a; //k个总体积w[cnt]=k*b; //k个总价值s-=k; //总数-k个k*=2; //k更新为2*k}//如果总数量s不等于2^k-1,则剩余的打包为一组//剩余s个if(s>0){cnt++;v[cnt]=s*a; //剩余s个总体积w[cnt]=s*b; //剩余s个总价值}}//总组数为cntn=cnt;//转化为cnt个01背包问题//遍历n组物品for(int i=1;i<=n;i++){//遍历背包体积for(int j=m;j>=v[i];j--){f[j]=max(f[j],f[j-v[i]]+w[i]);}}cout<<f[m]<<endl;return 0;
}
多重背包问题3
原题链接
AcWing 6. 多重背包问题3
题目描述
与上述完全背包1的描述完全相同,不同点在于数据范围的变化。
数据范围
0<N≤1000
0<V≤20000
0<vi,wi,si≤20000
数据范围的再次扩大,需要对代码进行再次的优化。采用单调队列/滑动窗口
观察状态计算公式,

f[i,j]与f[i,j-v]有相同项,但f[i,j-v]比f[i,j]多了一项,仔细观察发现,f[i,j]的max计算即为f[i,j-v]的前s项+w,即 f [ i , j ] = m a x ( f [ i − 1 , j ] , f [ i , j − v ] 的前 s 项 + w ) f[i,j]=max(f[i-1,j],f[i,j-v]的前s项+w) f[i,j]=max(f[i−1,j],f[i,j−v]的前s项+w) 。
且对于每个 f [ i , j ] , f [ i , j − v ] , f [ i , j − 2 v ] , ⋅ ⋅ ⋅ f[i,j],f[i,j-v],f[i,j-2v],··· f[i,j],f[i,j−v],f[i,j−2v],⋅⋅⋅ 都可通过其前一项的前s项进行递推运算。
画出数轴对应示意图如下,每次计算出前s项中的最大值。

由此观察我们可以发现,与 滑动窗口 及其相似!
滑动窗口主要利用单调队列解决了一组数中滑动的固定大小的窗口中的最大值/最小值。
下面详细说明 滑动窗口 的思想。
滑动窗口/单调队列
原题链接
AcWing 154. 滑动窗口
题目描述
给定一个数组。
有一个大小为 k的滑动窗口,它从数组的最左边移动到最右边。
你只能在窗口中看到 k个数字。
每次滑动窗口向右移动一个位置。
以下是一个例子:
该数组为 [1 3 -1 -3 5 3 6 7],k为 3。
| 窗口位置 | 最小值 | 最大值 |
|---|---|---|
| [1 3 -1] -3 5 3 6 7 | -1 | 3 |
| 1 [3 -1 -3] 5 3 6 7 | -3 | 3 |
| 1 3 [-1 -3 5] 3 6 7 | -3 | 5 |
| 1 3 -1 [-3 5 3] 6 7 | -3 | 5 |
| 1 3 -1 -3 [5 3 6] 7 | 3 | 6 |
| 1 3 -1 -3 5 [3 6 7] | 3 | 7 |
任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
题目分析
窗口可用队列来维护,队尾插入,队头弹出。
若求最小值,
如下列实例滑动窗口中,3<-3,-1<-3,且-3在后面,则只要-3在,则最小值一定是-3,3与-1的值一定不会被取到,

因此,提炼出,只要出现逆序对,即前面的数大于后面的数,则一定不会取到,则出队。
由此,可知,队列中的数一定是单调递增的( 单调队列 )。
则,队头存储的即为当前的最小值。
(可仔细看下方实例加深理解!!!)

求最大值同理。
完整代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1000010;
int n,k;
int a[N]; //存储原数组
int q[N]; //数组模拟队列,存储数组下标
int main(){scanf("%d%d",&n,&k); //n为数组长度,k为滑动窗口长度for(int i=0;i<n;i++) scanf("%d",&a[i]);int hh=0,tt=-1;//最小值for(int i=0;i<n;i++){//hh<=tt表明队列不为空//判断队头是否已经滑出窗口//窗口最前端的数组下标大于队首,则队头数出列if(hh<=tt&&i-k+1>q[hh]) hh++; //i-k+1(当前数组下标i-窗口长度k+1)为当前窗口最前端的数组下标//判断队尾数组是否大于当前数,若大于,出现逆序,则队尾的数弹出tt--while(hh<=tt&&a[q[tt]]>=a[i]) tt--;q[++tt]=i; //当前数入队//前k-1个数时,不输出答案if(i>=k-1) printf("%d ",a[q[hh]]);}puts("");//最大值hh=0,tt=-1;for(int i=0;i<n;i++){//判断队头是否已经滑出窗口//窗口最前端的数组下标大于队首,则队头数出列if(hh<=tt&&i-k+1>q[hh]) hh++; //i-k+1(当前数组下标i-窗口长度k+1)为当前窗口最前端的数组下标//判断队尾数组是否小于当前数,若小于,出现逆序,则队尾的数弹出tt--while(hh<=tt&&a[q[tt]]<=a[i]) tt--;q[++tt]=i; //当前数入队//前k-1个数时,不输出答案if(i>=k-1) printf("%d ",a[q[hh]]);}}
回到原来的多重背包问题,借用单调队列/滑动窗口的思想。
完整代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=20010;
int n,m;
int f[N]; //状态计算
int g[N]; //动态数组,动态存储前一个状态
int q[N]; //数组模拟队列
int main(){cin>>n>>m;for(int i=0;i<n;i++){int v,w,s;cin>>v>>w>>s; //体积,价值,数量//将上次计算的f赋给gmemcpy(g,f,sizeof f);//遍历体积for(int j=0;j<v;j++){int hh=0,tt=-1; //队头hh,队尾tt//滑动的一组数,j,j+v,j+2v···,每次右滑v,窗口长度为s*vfor(int k=j;k<=m;k+=v){//判断队头是否已滑出窗口if(hh<=tt&&q[hh]<k-s*v) hh++; //当前数k-窗口长度s*v =滑动窗口左端的数组下标//状态计算 f[i]=max(f[i],f[i-1]+相差的w)if(hh<=tt) f[k]=max(f[k],g[q[hh]]+(k-q[hh])/v*w); //当前数下标k-队头存储数组下标可得前后状态二者相差的w的个数//判断当前数是否大于队尾//队尾的状态计算值为g[q[tt]],窗口中为不带w的计算,因此要减去对应数量的w//(w数量的计算为当前队尾存储的下标q[tt]-最前端下标j)除以v,即为二者相差的距离,就是对应相差的w个数//当前数的状态计算值为g[k],窗口中为不带w的计算,因此要减去对应数量的w//(w数量的计算为当前数的下标k-最前端下标j)除以v,即为二者相差的距离,就是对应相差的w的个数while(hh<=tt&&g[q[tt]]-(q[tt]-j)/v*w<=g[k]-(k-j)/v*w) tt--;//当前数入队q[++tt]=k;}}}cout<<f[m]<<endl;return 0;
}
相关文章:
动态规划DP 背包问题 多重背包问题(朴素版+二进制优化+单调队列)
概览检索 动态规划DP 概览(点击链接跳转) 动态规划DP 背包问题 概览(点击链接跳转) 多重背包问题1 原题链接 AcWiing 4. 多重背包问题1 题目描述 有 N种物品和一个容量是 V的背包。 第 i 种物品最多有 si件,每件体…...
调试与错误修复:Cursor 如何成为你的编程助手
引言 调试是软件开发过程中最耗时且最具挑战性的环节之一。据统计,开发者平均将 50% 以上的编码时间 用于定位和修复错误。传统调试工具(如断点调试器、日志分析)虽能解决问题,但往往需要开发者手动追溯代码执行流程,…...
PHP 常用函数2025.02
PHP implode() 函数 语法 implode(separator,array) 参数描述separator可选。规定数组元素之间放置的内容。默认是 ""(空字符串)。array必需。要组合为字符串的数组。 技术细节 返回值:返回一个由数组元素组合成的字符串。PHP 版…...
浏览器查询所有的存储信息,以及清除的语法
要在浏览器的控制台中查看所有的存储(例如 localStorage、sessionStorage 和 cookies),你可以使用浏览器开发者工具的 "Application" 标签页。以下是操作步骤: 1. 打开开发者工具 在 Chrome 或 Edge 浏览器中…...
Paimon写入性能
写入性能 Paimon的写入性能与检查点密切相关,因此需要更大的写入吞吐量: 增加检查点间隔,或者仅使用批处理模式。增加写入缓冲区大小。启用写缓冲区溢出。如果您使用固定存储桶模式,请重新调整存储桶数量。 1 并行度 建议sink…...
Golang 并发机制-5:详解syn包同步原语
并发性是现代软件开发的一个基本方面,Go(也称为Golang)为并发编程提供了一组健壮的工具。Go语言中用于管理并发性的重要包之一是“sync”包。在本文中,我们将概述“sync”包,并深入研究其最重要的同步原语之一…...
排序算法与查找算法
1.十大经典排序算法 我们希望数据以一种有序的形式组织起来,无序的数据我们要尽量将其变得有序 一般说来有10种比较经典的排序算法 简单记忆为Miss D----D小姐 时间复杂度 :红色<绿色<蓝色 空间复杂度:圆越大越占空间 稳定性&…...
如何构建ObjC语言编译环境?构建无比简洁的clang编译ObjC环境?Windows搭建Swift语言编译环境?
如何构建ObjC语言编译环境? 除了在线ObjC编译器,本地环境Windows/Mac/Linux均可以搭建ObjC编译环境。 Mac自然不用多说,ObjC是亲儿子。(WSL Ubuntu 22.04) Ubuntu可以安装gobjc/gnustep和gnustep-devel构建编译环境。 sudo apt-get install gobjc gnus…...
数据结构课程设计(三)构建决策树
3 决策树 3.1 需求规格说明 【问题描述】 ID3算法是一种贪心算法,用来构造决策树。ID3算法起源于概念学习系统(CLS),以信息熵的下降速度为选取测试属性的标准,即在每个节点选取还尚未被用来划分的具有最高信息增益的…...
深度剖析八大排序算法
欢迎并且感谢大家指出我的问题,由于本人水平有限,有些内容写的不是很全面,只是把比较实用的东西给写下来,如果有写的不对的地方,还希望各路大牛多多指教!谢谢大家!🥰 在计算机科学领…...
python-leetcode-二叉树的层序遍历
102. 二叉树的层序遍历 - 力扣(LeetCode) # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right from coll…...
毕业设计:基于深度学习的高压线周边障碍物自动识别与监测系统
目录 前言 课题背景和意义 实现技术思路 一、算法理论基础 1.1 卷积神经网络 1.2 目标检测算法 1.3 注意力机制 二、 数据集 2.1 数据采集 2.2 数据标注 三、实验及结果分析 3.1 实验环境搭建 3.2 模型训练 3.2 结果分析 最后 前言 📅大四是整个大学…...
顺序表(ArrayList)
1、简介 顺序表是用一段物理地址连续 的存储单元依次存储数据元素的线性结构,一般情况下 采用数组存储 。在 数组 上完成数据的增删查改。( 顺序表的底层结构是一个数组 ) 2、顺序表的实现 下面是顺序表的一些基本成员和方法,能够…...
【Hadoop】Hadoop的HDFS
这里写目录标题 HDFS概述HDFS产出背景及定义HDFS产生背景HDFS定义 HDFS优缺点HDFS优点HDFS缺点 HDFS组成架构HDFS文件块大小 HDFS的Shell操作常用命令实操准备工作上传下载HDFS直接操作 HDFS的API操作客户端环境准备HDFS的API案例实操HDFS文件上传HDFS文件下载HDFS文件更名和移…...
C++ Primer 迭代器
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
使用 Postman 进行 API 测试:从入门到精通
使用 Postman 进行 API 测试:从入门到精通 使用 Postman 进行 API 测试:从入门到精通一、什么是 API 测试?二、Postman 简介三、环境搭建四、API 测试流程1. 收集 API 文档2. 发送基本请求示例:发送 GET 请求示例代码(…...
Leetcode面试高频题分类刷题总结
https://zhuanlan.zhihu.com/p/349940945 以下8个门类是面试中最常考的算法与数据结构知识点。 排序类(Sort): 基础知识:快速排序(Quick Sort), 归并排序(Merge Sort)的…...
8.原型模式(Prototype)
动机 在软件系统中,经常面临着某些结构复杂的对象的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。 之前的工厂方法和抽象工厂将抽象基类和具体的实现分开。原型模式也差不多&#…...
简单介绍一下什么是OpenFeign
OpenFeign是什么? OpenFeign是一个声明式的Http客户端,它可以用来发起Http请求 它主要用于SpringCloud微服务之间的通讯,让调用另一个服务的Java方法和调用本地方法一样快速和便捷 之前我们是用RestTemplate写一大堆东西发起Http请求远程调…...
力扣动态规划-20【算法学习day.114】
前言 ###我做这类文章一个重要的目的还是记录自己的学习过程,我的解析也不会做的非常详细,只会提供思路和一些关键点,力扣上的大佬们的题解质量是非常非常高滴!!! 习题 1.网格中的最小路径代价 题目链接…...
Codeforces Round 1002 (Div. 2)(部分题解)
补题链接 A. Milya and Two Arrays 思路:题意还是比较好理解,分析的话我加了一点猜的成分,对a,b数组的种类和相加小于4就不行,蒋老师的乘完后小于等于2也合理。 AC代码: #include <bits/stdc.h> u…...
在线销售数据集分析:基于Python的RFM数据分析方法实操训练
一、前言 个人练习,文章用于记录自己的学习练习过程,分享出来和大家一起学习。 数据集:在线销售数据集 分析方法:RFM分析方法 二、过程 1.1 库的导入与一些必要的初始设置 import pandas as pd import datetime import matplo…...
小程序设计和开发:要如何明确目标和探索用户需求?
一、明确小程序的目标 确定业务目标 首先,需要明确小程序所服务的业务领域和目标。例如,是一个电商小程序,旨在促进商品销售;还是一个服务预约小程序,方便用户预订各类服务。明确业务目标有助于确定小程序的核心功能和…...
【C语言深入探索】:指针高级应用与极致技巧(二)
目录 一、指针与数组 1.1. 数组指针 1.2. 指向多维数组的指针 1.2.1. 指向多维数组元素的指针 1.2.2. 指向多维数组行的指针 1.3. 动态分配多维数组 1.4. 小结 二、指针与字符串 2.1. 字符串表示 2.2. 字符串处理函数 2.3. 代码示例 2.4. 注意事项 三、指针与文件…...
2.策略模式(Strategy)
定义 定义一系列算法,把它们一个个封装起来,并且使他们可互相替换(变化)。该模式使算法可独立于使用它的客户程序(稳定)而变化(拓展,子类化)。 动机(Motiva…...
手写MVVM框架-构建虚拟dom树
MVVM的核心之一就是虚拟dom树,我们这一章节就先构建一个虚拟dom树 首先我们需要创建一个VNode的类 // 当前类的位置是src/vnode/index.js export default class VNode{constructor(tag, // 标签名称(英文大写)ele, // 对应真实节点children,…...
【Blazor学习笔记】.NET Blazor学习笔记
我是大标题 我学习Blazor的顺序是基于Blazor University,然后实际内容不完全基于它,因为它的例子还是基于.NET Core 3.1做的,距离现在很遥远了。 截至本文撰写的时间,2025年,最新的.NET是.NET9了都,可能1…...
C++11中的bind
官方文档对于bind接口的概述解释:Bind function arguments 在C11中,std::bind 是一个非常有用的工具,用于将函数、成员函数或函数对象与特定的参数绑定在一起,生成一个新的可调用对象。std::bind 可以用于部分应用函数参数、改变…...
llama.cpp的C语言API使用
我们知道,一般运行大语言模型都是在Python上运行的,可是Python的性能太差了,不适合用于生产环境,因此可以采用llama.cpp提供的API在C语言上运行大模型。 llama.cpp的下载 Windows下的下载 我们需要下载llama.cpp的两个部分&…...
鼠标拖尾特效
文章目录 鼠标拖尾特效一、引言二、实现原理1、监听鼠标移动事件2、生成拖尾元素3、控制元素生命周期 三、代码实现四、使用示例五、总结 鼠标拖尾特效 一、引言 鼠标拖尾特效是一种非常酷炫的前端交互效果,能够为网页增添独特的视觉体验。它通常通过JavaScript和C…...

