背包 DP 详解

文章目录

  • 背包DP
    • 01 背包
    • 完全背包
    • 多重背包
      • 二进制优化
      • 单调队列优化
  • 小结

背包DP

背包 DP,说白了就是往一个背包里扔东西,求最后的最大价值是多少,一般分为了三种:01 背包、完全背包和多重背包。而 01 背包则是一切的基础。

01 背包

特点:每个物品只有一个,只能选择装这个物品或者不装。

这时我们一般设一个 dp[i][j] 表示前 i i i 个物品再总体积为 j j j 的情况下装的最大价值是多少。所以 01 背包的时间复杂度为 O ( n 2 ) O(n^2) O(n2),这时我们可以得到这样一个状态转移方程:

d p i , j = max ⁡ { d p i − 1 , j , d p i − 1 , j − v i + w i } dp_{i,j}=\max\{dp_{i-1,j},dp_{i-1,j-v_i}+w_i\} dpi,j=max{dpi1,j,dpi1,jvi+wi}

这里的 v i v_i vi 指当前物品所占的体积, w i w_i wi 指当前物品的价值。

那这是怎么一回事呢?我们可以这样想:我要么不选这件物品,那么总价值就和 d p i − 1 , j dp_{i-1,j} dpi1,j 一样(即同样的体积在前 i − 1 i-1 i1 件物品中的最大价值),如果选了,那就和前 i − 1 i-1 i1 件物品在体积为 j − v i j-v_i jvi 的情况下的最大价值再加上当前物品的价值 w i w_i wi 一样,那再取其中的最大值就行了。

但是,我们会发现一个问题:如果你的总体积为 V V V,总物品数为 n n n,那你的空间复杂度就是 O ( V n ) O(Vn) O(Vn),这可是一个极其庞大的数字,这是我们就要请上我们的滚动背包!

滚动背包旨在通过观察状态转移方程看看哪些空间没用从而可以省掉。比如说上面的状态转移方程,我们会发现 DP 的第一维只与当前状态与上一状态有关,而与其他的无关,所以其它空间就是被浪费了的,所以我们只需要把第一维开个 2 2 2,而不用开 n n n,这样,空间复杂度就被压到了 O ( 2 V ) O(2V) O(2V),状态转移方程也就变成了这样:

d p i m o d 2 , j = max ⁡ { d p ( i − 1 ) m o d 2 , j , d p ( i − 1 ) m o d 2 , j − v i + w i } dp_{i\bmod2,j}=\max\{dp_{(i-1)\bmod2,j},dp_{(i-1)\bmod2,j-v_i}+w_i\} dpimod2,j=max{dp(i1)mod2,j,dp(i1)mod2,jvi+wi}

上面这种方法适用于初学者,因为它清楚直观、浅显易懂。

让我们先来看一道例题:

N N N 件物品和一个容量是 V V V 的背包。每件物品只能使用一次。

i i i 件物品的体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出最大价值。

很经典的一道 01 背包问题,直接用上面的公式就对了。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,vv,v[1006],w[1006],dp[2][1006]; 
signed main()
{cin>>n>>vv;for(int i=1;i<=n;i++){cin>>v[i]>>w[i];}for(int i=1;i<=n;i++){for(int j=0;j<=vv;j++){dp[i%2][j]=dp[(i-1)%2][j];if(j>=v[i]){dp[i%2][j]=max(dp[(i-1)%2][j],dp[(i-1)%2][j-v[i]]+w[i]);}}}cout<<dp[n%2][vv];return 0;
}

当然,我们也可以在这个的基础上优化成一维滚动背包,但是中间的循环需要颠倒过来一下,具体我们可以这么想:我们上面的状态转移方程再更新时原本的和现在的状态是不会受影响的,而如果我们正着循环,可能就会出现这种情况:

按照我们之前的状态转移方程,我们应该拿原本的旧的状态来更新新的状态,但现在我们却拿我们计算好了的新的状态来更新更新的状态,这是完全不符合的,而倒过来循环正好就能避免这种事情的发生。(想通了的同学继续看,没想通的同学抠破脑袋想。)

所以上面的代码还可以写成这样:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,vv,v[1006],w[1006],dp[1006]; 
int main()
{cin>>n>>vv;for(int i=1;i<=n;i++){cin>>v[i]>>w[i];}for(int i=1;i<=n;i++){for(int j=vv;j>=v[i];j--){dp[j]=max(dp[j],dp[j-v[i]]+w[i]);}}cout<<dp[vv];return 0;
}

完全背包

特点:每个物体有无穷多个,可以无限取同一个物品。

这时的改动很小,只需要把上面 01 背包优化成一维时的循环改成正着循环就行了。这又是为啥呢?

我们前面不是说了吗:正着循环会把更新好的状态拿去更新当前状态。如果是 01 背包每种只能用一次,那肯定不行。但是完全背包可以多次放一个物品啊,当前的最新状态对于另一个状态来讲可能只是一个旧状态罢了。

所以,我们把上面的代码稍作改动:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,vv,v[1006],w[1006],dp[1006]; 
int main()
{cin>>n>>vv;for(int i=1;i<=n;i++){cin>>v[i]>>w[i];}for(int i=1;i<=n;i++){for(int j=v[i];j<=vv;j++){dp[j]=max(dp[j],dp[j-v[i]]+w[i]);}}cout<<dp[vv];return 0;
}

就水灵灵的 A C \color{green}{AC} AC 了!

(讲的稍微有点快,没看懂的同学可以回去再看。)

多重背包

特点:每件物品有一定数量,所装的物品数量不能超过这个值。

还是拿一道例题讲一下:

N N N 种物品和一个容量是 V V V 的背包。第 i i i 种物品最多有 s i s_i si 件,每件体积是 v i v_i vi,价值是 w i w_i wi
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

外面两层循环跟 01 背包一模一样,但是因为有数量限制,里面需要多加一层循环:

for(int k=0;k<=s[i]&&v[i]*k<=j;k++)

这层循环就是循环你要拿或者是不拿 k k k 个物品。

然后我们写状态转移方程:

d p i , j = max ⁡ { d p i − 1 , j , d p i − 1 , j − k × v i + k × w i } dp_{i,j}=\max\{dp_{i-1,j},dp_{i-1,j-k\times v_i}+k\times w_i\} dpi,j=max{dpi1,j,dpi1,jk×vi+k×wi}

再根据与 01 背包一样的套路优化一下即可。

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,vv,v[106],w[106],s[106],dp[106]; 
signed main()
{cin>>n>>vv;for(int i=1;i<=n;i++){cin>>v[i]>>w[i]>>s[i];}for(int i=1;i<=n;i++){for(int j=vv;j>=v[i];j--){for(int k=0;k<=s[i]&&v[i]*k<=j;k++){dp[j]=max(dp[j],dp[j-v[i]*k]+w[i]*k);}}}cout<<dp[vv];return 0;
}

二进制优化

但是(一般一切顺利的时候搜会有个“但是”),我们稍加计算发现它的时间复杂度达到了恐怖的 O ( V × ∑ s i ) O(V\times\sum s_i) O(V×si),这个时间复杂度是极高的,为了降低时间复杂度,我们采取了一种优化:转为 01 背包问题。

因为我们有至多 s i s_i si 个,那么我们可以选择的个数有哪些呢?这时,聪明的计算机学家们就想到了一种办法:转二进制

这个转二进制有什么好处呢?好处可大了去了。因为我们知道任何一个数倍拆成二进制后是可以拼凑出再 s i s_i si 以内的所有数的,而对于当前这个数加不加上又是一个 01 背包问题。

也就是这样:假设 s i = 7 s_i=7 si=7,那么 7 7 7 写成二进制就是 111 111 111,拆成二的幂次相加就是 2 2 + 2 1 + 2 0 2^2+2^1+2^0 22+21+20,而对于每一个数选不选进来凑成一个小于 s i s_i si 的数是个 01 背包问题。于是我们就将多重背包转化成了 01 背包。而对于每一个数再乘 v i v_i vi 表示新的体积,乘 w i w_i wi 表示新的质量,然后做 01 背包就行。

这样我们就将时间复杂度降到了 O ( V × ∑ log ⁡ 2 ( s i ) ) O(V\times\sum\log_2(s_i)) O(V×log2(si)),大大优化了啊!

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
struct binary{int v,w;
};
int n,vv,v[1006],w[1006],s[1006],dp[2006]; 
vector<binary>v1;
signed main()
{cin>>n>>vv;for(int i=1;i<=n;i++){cin>>v[i]>>w[i]>>s[i];}for(int i=1;i<=n;i++){for(int j=1;j<=s[i];j*=2){s[i]-=j;v1.push_back({v[i]*j,w[i]*j});}if(s[i]>=0){v1.push_back({v[i]*s[i],w[i]*s[i]});}}for(auto i:v1){for(int j=vv;j>=i.v;j--){dp[j]=max(dp[j],dp[j-i.v]+i.w);}}cout<<dp[vv];return 0;
}

单调队列优化

注:一般情况下只会卡到二进制优化,所以单调队列优化看不懂的同学可以直接跳过。

二进制优化已经很好了,但还不是最好,真正好的直接优化到了 O ( N × V ) O(N\times V) O(N×V),让我们有请单调队列优化

~~由于本作者实在是太菜了,竟然没看懂单调队列优化,~~感兴趣的同学可以参考这篇文章。

小结

这篇文章带你梳理了三种基本背包:01 背包、完全背包与多重背包,至于更多的背包模型请大家搜索背包九讲,那里有更多的大佬在等着你们。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/79698.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二级评论列表-Java实现

二级评论列表是很常见的功能&#xff0c;文章记录了新手用Java实现的具体逻辑。 整体实现逻辑是先用2个sql&#xff0c;分别查出两层数据。然后用java在service中实现数据组装&#xff0c;返给前端。这种实现思路好处是SQL简洁&#xff0c;逻辑分明&#xff0c;便于维护。 一…

快速入手-基于python和opencv的人脸检测

1、安装库 pip install opencv-python 如果下载比较卡的话&#xff0c;指向国内下载地址&#xff1a; pip3 install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple 2、下载源码 https://opencv.org/ windows11对应的版本下载&#xff1a; https://pan.baidu…

GitLab本地安装指南

当前GitLab的最新版是v17.10&#xff0c;安装地址&#xff1a;https://about.gitlab.com/install/。当然国内也可以安装极狐GitLab版本&#xff0c;极狐GitLab 是 GitLab 中国发行版&#xff08;JH&#xff09;。极狐GitLab支持龙蜥&#xff0c;欧拉等国内的操作系统平台。安装…

OpenCv高阶(六)——图像的透视变换

目录 一、透视变换的定义与作用 二、透视变换的过程 三、OpenCV 中的透视变换函数 1. cv2.getPerspectiveTransform(src, dst) 2. cv2.warpPerspective(src, H, dsize, dstNone, flagscv2.INTER_LINEAR, borderModecv2.BORDER_CONSTANT, borderValue0) 四、文档扫描校正&a…

资源-又在网上淘到金了

前言&#xff1a; 本期再分享网上冲浪发现的特效/动画/视频资源网站。 一、基本介绍&#xff1a; mantissa.xyz&#xff0c;about作者介绍为&#xff1a;Midge “Mantissa” Sinnaeve &#xff08;米奇辛纳夫&#xff09;是一位屡获殊荣的艺术家和导演&#xff0c;提供动画、…

Linux疑难杂惑 | 云服务器重装系统后vscode无法远程连接的问题

报错原因&#xff1a;本地的known_hosts文件记录服务器信息与现服务器的信息冲突了&#xff0c;导致连接失败。 解决方法&#xff1a;找到本地的known_hosts文件&#xff0c;把里面的所有东西删除后保存就好了。 该文件的路径可以在报错中寻找&#xff1a;比如我的路径就是&a…

FFMPEG-视频解码-支持rtsp|rtmp|音视频文件(低延迟)

本人亲测解码显示对比延迟达到7到20毫秒之间浮动兼容播放音视频文件、拉流RTSP、RTMP等网络流 基于 Qt 和 FFmpeg 的视频解码播放器类,继承自 QThread,实现了视频流的解码、播放控制、帧同步和错误恢复等功能 工作流程初始化阶段: 用户设置URL和显示尺寸 调用play()启动线程解…

【音视频】音视频FLV合成实战

FFmpeg合成流程 示例本程序会⽣成⼀个合成的⾳频和视频流&#xff0c;并将它们编码和封装输出到输出⽂件&#xff0c;输出格式是根据⽂件扩展名⾃动猜测的。 示例的流程图如下所示。 ffmpeg 的 Mux 主要分为 三步操作&#xff1a; avformat_write_header &#xff1a; 写⽂件…

全链路开源数据平台技术选型指南:六大实战工具链解析

在数字化转型加速的背景下&#xff0c;开源技术正重塑数据平台的技术格局。本文深度解析数据平台的全链路架构&#xff0c;精选六款兼具创新性与实用性的开源工具&#xff0c;涵盖数据编排、治理、实时计算、联邦查询等核心场景&#xff0c;为企业构建云原生数据架构提供可落地…

JAVA设计模式——(1)适配器模式

JAVA设计模式——&#xff08;1&#xff09;适配器模式 目的理解实现优势 目的 将一个类的接口变换成客户端所期待的另一种接口&#xff0c;从而使原本因接口不匹配而无法一起工作的两个类能够在一起工作。 理解 可以想象成一个国标的插头&#xff0c;结果插座是德标的&…

Qt C++ 解析和处理 XML 文件示例

使用 Qt C 解析和处理 XML 文件 以下是使用 Qt C 实现 XML 文件处理的几种方法&#xff0c;包括解析、创建和修改 XML 文件。 1. 使用 QXmlStreamReader (推荐方式) #include <QFile> #include <QXmlStreamReader> #include <QDebug>void parseXmlWithStr…

坐标上海,20~40K的面试强度

继续分享最新的面经&#xff0c;面试的岗位是上海某公司的Golang开发岗&#xff0c;给的薪资范围是20~40K&#xff0c;对mongodb要求熟练掌握&#xff0c;所以面试过程中对于mongodb也问的比较多。 下面是我整理好的面经&#xff08;去除了项目相关的问题&#xff09;&#xf…

B端管理系统:企业运营的智慧大脑,精准指挥

B端管理系统的定义与核心功能 B端管理系统&#xff08;Business Management System&#xff09;是专门设计用于支持企业内部运作和外部业务交互的一套软件工具。它集成了多种功能模块&#xff0c;包括但不限于客户关系管理(CRM)、供应链管理(SCM)、人力资源管理(HRM)以及财务管…

IDE中使用Spring Data Redis

步骤一&#xff1a;导入Spring Data Redis的maven坐标 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency> 步骤二&#xff1a;配置Redis数据源 步骤三&…

ARINC818协议的帧格式

SOFi:sof initiale;这个是第一个ADVB帧的SOF开始&#xff0c;一帧只有一个SOFi。 SOFn:sof normal;这个是非首个ADVB帧的SOF头的normal头。 Vsync为场同步&#xff0c;两个SOFi之间为Vsync信号&#xff0c;也就是一帧&#xff0c;也就是VS信号。 Hsync为行同步&#xff0c;如果…

Git核心命令

Git核心命令完全指南&#xff1a;从入门到高效协作 前言 在软件开发领域&#xff0c;Git已成为现代版本控制的代名词。据统计&#xff0c;全球超过90%的开发团队使用Git进行代码管理。然而&#xff0c;许多开发者仅停留在基础命令的机械使用层面&#xff0c;未能真正掌握Git命…

【计算机视觉】CV实战项目- Face-and-Emotion-Recognition 人脸情绪识别

Face-and-Emotion-Recognition 项目详细介绍 项目概述项目功能项目目录结构项目运行方式1. 环境准备2. 数据准备3. 模型训练4. 模型运行 常见问题及解决方法1. **安装依赖问题**2. **数据集问题**3. **模型训练问题**4. **模型运行问题** 项目实战建议项目参考文献 项目概述 F…

java lambda

案例1 lambda表达式看做成一个函数对象 方法引用 1.Math是类型&#xff0c;max是静态方法 2.Student是对象&#xff0c;getName是非静态方法 3.对象&#xff1a;&#xff1a;非静态方法 4.类型&#xff1a;&#xff1a;new关键字 练习1 假设已有对象 常见函数接口 predicate…

并发网路通信-套接字通信

套接字通信就是网络通信 在网络通信时,客户端和服务器的比例是N:1 服务器如何处理多个客户端的请求 并发处理方式 1.多线程并发处理->线程池并发处理,线程池可以对多个线程进行管理 2.多进程->进程池 3.io多路转接,使用select或者epoch进行处理,使用io转接函数…

AI当前状态:有哪些新技术

一、到目前为址AI领域出现的新技术 到目前为止&#xff0c;AI领域涌现了许多令人兴奋的新技术。以下是一些关键的进展&#xff0c;涵盖了从基础模型到实际应用的多个方面&#xff1a; 1. 更强大的大型语言模型 (LLMs): 性能提升: 新一代LLM&#xff0c;例如OpenAI的GPT-4o和…