详解动态规划之01背包问题及其空间压缩(图文并茂+例题讲解)

1. 动态规划问题的本质

  • 记忆化地暴力搜索所有可能性来得到问题的解

我们常常会遇到一些问题,需要我们在n次操作,且每次操作有k种选择时,求出最终需要的最小或最大代价。处理类似的问题,我们一般需要遍历所有的可能性(相当于走一遍所有的路径),然后找到我们所需要的解。
很明显我们可以构成一棵“决策树”,假设n=2,k=3,那么:

在这里插入图片描述

我们可以通过DFS或者BFS来遍历整棵树,从而搜寻到我们需要的结果。
时间复杂度:O(k^n)

但是我们可以看到,时间复杂度是指数级别的,一旦n的数级够大,那么时间代价是极大的。

这个时候,我们就需要用到动态规划的思想了。我们可以发现,执行决策的时候,经常会出现重复决策的地方,在第一次操作的时候有三种决策,在第二次操作时,在执行了决策1的情况下,又执行了三次相同的决策。这些重复的路径所导致的结果是相同的,那么我们完全可以直接调用之前的结果,大大减少了时间代价。
那么我们是否可以将遍历过的路径保存在一个数组(容器)中,如果我们遇到了重复的路径,直接访问之前访问过的路径的结果,并将其转移过来到当前的位置。

所以其本质上还是暴力搜索了所有可能,只不过加上了记忆曾经遍历过的路径

2. 01背包问题

01背包问题是主要解决n个物品的放置问题,对于物品的放与不放,如果放的话改怎么放的决策进行搜索。
下面会根据一道蓝桥杯的典型例题来辅助讲解01背包及其空间压缩的问题。

题目链接:

https://www.luogu.com.cn/problem/P8742

题面描述

在这里插入图片描述

思路分析

  • 显而易见,每个砝码的放置有三种情况:1.放左边 2.放右边 3.不放。如果使用DFS遍历所有情况,那么时间复杂度为3^n,n的取值可以到100,必然超时。
  • 假设当我们放完第一个物品时,我们开始放第二个物品,我们可以单独放;可以放在第一个物品的同侧,也可以放在第一个物品的异侧。后两种情况是建立在第一个物品放的情况下结果,所以我们很明显要知道是否存在物品一放了的重量是否存在。
  • 我们定义dp[i][j]的含义是之考虑前i个物品放置的所有情况下,背包容量为j时,是否存在
    这样的物品放置的情况刚好装满。
  • 则状态转移方程为:
    1.当不放第i个物品时: dp[i][j]=dp[i-1][j]
    2.当只有第i个物品,即如果j==a[i]dp[i][j]=1
    3.当减少第i个物品,即如果dp[i-1][j+a[i]]==1dp[i][j]=1
    4.当增加第第i个物品,即如果dp[i-1][|j-a[i]|==1dp[i][j]=1
  • 我们来分析一下状态转移方程:
    因为我们有三种决策:1.放左边 2.放右边 3.不放。
  1. 不放物品就不必说了,直接转移保存上次的状态即可。其次,我们可以单独放一个物品,所以有了方程2;然后是在放前i-1的物品的情况下,将第i个物品放在左侧或右侧。
  2. 假设我们以左侧的重量来遍历j,那么放在左侧就是加上这个砝码,那么就需要记忆化查询没有加上这个砝码时的重量是否存在,若存在,那么j可以称出,否则不能;放在右侧也是同理。

下面是AC代码

#include "bits/stdc++.h"
using namespace std;
#define int long long
#define endl '\n'
const int MAXN=105;
const int MAXM=1e5+10;
int n;
int a[MAXN];
int dp[MAXN][MAXM];
void solve() {cin >> n;int s=0;for (int i = 1; i <= n; ++i) {cin >> a[i];s+=a[i];}dp[0][0] = 1;for (int i = 1; i <= n; ++i) {for (int j = 0; j <= s; ++j) {dp[i][j] = dp[i-1][j];if (j == a[i]) dp[i][j] = 1;if (j+a[i] <= s && dp[i-1][j+a[i]] == 1) dp[i][j] = 1;if (dp[i-1][abs(j-a[i])] == 1) dp[i][j] = 1;}}int ans=0;for (int j = 1; j <= s; ++j) {if (dp[n][j] == 1) ans++;}cout << ans << endl;
}signed main() {ios :: sync_with_stdio(false);cin.tie(nullptr);int t=1;//cin >> t;while (t--) solve();
}

3. 如何进行空间压缩

我们可以发现,每次遍历到第i个砝码的时候,我们只会用到上一次(第i-1)时的状态,从0~i-2的空间不再被使用,那么我们完全可以将其压缩掉,达到优化空间复杂度的目的。

那么该如何进行空间压缩呢?

这里我们引入了滚动数组的概念,将dp数组想象成动态滚动更新的。可以视作将数组分为两部分:一部分是i-1状态下的数组;另一部分是i状态下的数组:
在这里插入图片描述
我们在增加第i个物品的时候,需要找到未考虑第i个物品时(i-1状态)且未增加第i个物品的重量(j-a[i])这个时候的状态,然后考虑进行状态转移。
假设如上图所示,我们现在遍历到重量为6的状态,那么6-a[i]的质量6的前面(保证a[i]范围的合理性),而6的前面刚好都是i-1状态下的,并没有被更新。就这样一直滚动,知道数组全部被更新为i状态。
这也是为什么背包问题被压缩为一维后,需要逆序遍历背包容量的原因。那么同理,如果需要减少第i个物品,那么我们要拿到i-1下的状态,那就要顺序遍历

压缩后的代码

#include "bits/stdc++.h"
using namespace std;
#define int long long
#define endl '\n'
const int MAXN=105;
const int MAXM=1e5+10;
int n;
int a[MAXN];
int dp[MAXM];
void solve() {cin >> n;int s=0;for (int i = 1; i <= n; ++i) {cin >> a[i];s+=a[i];}dp[0] = 1;for (int i = 1; i <= n; ++i) {for (int j = s; j >= a[i]; --j) {if (dp[j-a[i]] == 1) dp[j] = 1;}}for (int i = 1; i <= n; ++i) {for (int j = 1; j <= s-a[i]; ++j) {if (dp[j+a[i]] == 1) dp[j] = 1;}}int ans=0;for (int j = 1; j <= s; ++j) {if (dp[j] == 1) ans++;}cout << ans << endl;
}signed main() {ios :: sync_with_stdio(false);cin.tie(nullptr);int t=1;//cin >> t;while (t--) solve();
}

总结

  • 总而言之,言而总之。动态规划问题的核心是利用空间换取时间,将曾经经过的状态保存下来,当在往后的遍历中如果需要该状态就不必再次重复一遍,只需直接访问当时的状态结果即可。
  • 而滚动数组的用法很巧妙地将上一层地状态保存下来的同时完成了对当前状态的更新。
    希望此文章会对您对动态规划的学习有一定帮助~

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

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

相关文章

SpringMVC核心组件之HandlerMapping详解

文章目录 前言一、AbstractHandlerMapping抽象类initApplicationContextgetHandler 二、MatchableHandlerMapping类二、AbstractUrlHandlerMapping类 前言 当一个web请求到来时&#xff0c;DispatcherServlet负责接收请求并响应结果。DispatcherServlet首先需要找到当前请求对…

普通人也能创业!轻资产短视频带货项目,引领普通人实现创业梦想

在这个信息爆炸的时代&#xff0c;创业似乎成为了越来越多人的梦想。然而&#xff0c;传统的创业模式 keJ0277 往往伴随着高昂的资金投入和复杂的管理流程&#xff0c;让许多普通人望而却步。然而&#xff0c;现在有一种轻资产短视频带货项目正在悄然兴起&#xff0c;它以其低…

2024做安全测试必须要知道的几种方法!

前言 安全性测试(Security Testing)是指有关验证应用程序的安全等级和识别潜在安全性缺陷的过程&#xff0c;其主要目的是查找软件自身程序设计中存在的安全隐患&#xff0c;并检查应用程序对非法侵入的防范能力&#xff0c;安全指标不同&#xff0c;测试策略也不同。 但安全…

『Apisix安全篇』快速掌握APISIX Basic-Auth插件高效使用

&#x1f4e3;读完这篇文章里你能收获到 &#x1f468;‍&#x1f4bb; 学习如何快速安装并配置APISIX Basic-Auth插件&#xff0c;为您的API安全保驾护航。&#x1f6e0;️ 文章详细介绍了如何创建带有basic-auth配置的Consumer&#xff0c;以及如何在Route中启用该插件。&am…

微信自主创建表单投票小程序源码系统 带充值刷礼物功能 附带源代码以及完整的安装部署教程

系统概述 本小程序实现的核心功能包括&#xff1a;用户注册登录、表单提交投票、查看投票结果、在线充值以及赠送礼物等。其中&#xff0c;投票表单可以根据实际需求进行自定义设置&#xff0c;满足不同类型的调查或评选活动。同时&#xff0c;通过引入第三方支付接口&#xf…

Django Celery 的配置及使用---最详细教程

Django Celery 的配置及使用 Redis提供队列消息功能 一、安装redis 系统版本&#xff1a;Ubuntu 20.041、获取最新软件包 sudo apt update sudo apt install redis-server2、安装完成后&#xff0c;Redis服务器会自动启动。查看redis是否启动成功 sudo systemctl status …

LLM大模型多模态面试题(二)

1. 介绍transformer算法 Transformer本身是一个典型的encoder-decoder模型&#xff0c;Encoder端和Decoder端均有6个Block&#xff0c;Encoder端的Block包括两个模块&#xff0c;多头self-attention模块以及一个前馈神经网络模块&#xff1b;Decoder端的Block包括三个模块&…

uniapp 实现下拉刷新 下滑更新

效果图 在app或者小程序中向下滑动 会出现刷新数据 ,而上拉到底 需要更新数据 功能实现 主要俩种方式 依赖生命周期 在page.json中开启 page.json "style" : {"navigationBarTitleText" : "小小练习","backgroundTextStyle": &qu…

狙击策略专用术语以及含义,WeTrade3秒讲解

想必各位交易高手对狙击策略不会陌生吧!但你想必不知道狙击策略的开发者为了推广狙击策略&#xff0c;在狙击策略基础的经典技术分析理论引入了自己的术语。今天WeTrade众汇和各位投资者继续了解狙击策略专用术语以及含义。 一.BL 银行级别(BL)是前一日线收盘的级别。时间是格…

微信小程序开发中怎么配置SSL证书?

在微信小程序开发中&#xff0c;配置SSL证书主要用于实现HTTPS请求&#xff0c;以保证数据传输的安全性。以下是配置SSL证书的基本步骤&#xff1a; 一、获取SSL证书 首先&#xff0c;你需要获取一个有效的SSL证书。SSL证书可以被广泛信任的证书颁发机构申请&#xff0c;如Jo…

rocketmq的顺序消息开发注意事项

1. 参考消息重试&#xff0c;要对 MaxReconsumeTimes进行设置。之前就是因为没有进行设置&#xff0c;导致了队头阻塞问题。 rokcetmq和kafka一样&#xff0c;当顺序消息写入的多个队列中后&#xff0c;如果是顺序消息&#xff0c;当前的队列的队头一直消费失败的时候&#x…

JVM运行时内存:本地方法接口与本地方法栈

文章目录 1. 什么是本地方法&#xff1f;2. 为什么要使用Native Method&#xff1f;3. 本地方法现状 运行时内存整体结构如下图所示: 1. 什么是本地方法&#xff1f; 简单地讲&#xff0c;一个Native Method就是一个Java调用非 Java 代码的接口。一个Native Method是这样一个 …

【Linux】linux | 配置系统日志 | 安全日志 | 操作日志 | 登录日志

一、诉求 1、linux服务器开启日志功能&#xff0c;并记录10个月的登录 二、操作 1、进入目录 cd /etc 2、编辑配置 vi logrotate.conf 3、复制配置 /var/log/wtmp {monthlycreate 0664 root utmpminsize 1Mrotate 10 }/var/log/btmp {missingokmonthlycreate 0600 root …

vue2人力资源项目9权限管理

页面搭建 <template><div class"container"><div class"app-container"><el-button size"mini" type"primary">添加权限</el-button><el-table-column label"名称" /><el-table-co…

Spring Boot代码案例(计算器、登录、留言板)

文章目录 一、计算器二、登录2.1 判断账号密码是否正确2.2 根据不同的用户作出不同反应 三、留言板3.1 提交数据3.2 展示所有数据 四、Lombok 工具包4.1 场景介绍4.2 如何使用 五、Edit Starters插件六、项目如何Debug七、项目命名规范 一、计算器 导入前端文件后端代码&#…

AI Agent是什么?未来如何发展

AI Agnt是什么 AI代理&#xff08;AI Agent&#xff09;是指一种利用人工智能技术来执行特定任务或解决特定问题的自主软件程序。这些代理通过学习和模拟人类行为或特定领域的知识&#xff0c;能够在无需人为干预的情况下完成复杂的任务。AI代理广泛应用于多个领域&#xff0c…

SSM【Spring SpringMVC Mybatis】—— SpringMVC

目录 1、初识SpringMVC 1.1 SpringMVC概述 1.2 SpringMVC处理请求原理简图 2、SpringMVC搭建框架 2.1 搭建SpringMVC框架 3、RequestMapping详解 3.1 RequestMapping注解位置 3.2 RequestMapping注解属性 3.3 RequestMapping支持Ant 风格的路径&#xff08;了解&#…

【Linux玩物志】Linux环境开发基本工具使用(1)——vim

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; Linux开发工具 首先我们要知道vim是什么&#xff1f; vi&#xff08;Visual Editor&#xff09;是由美国程序员比尔乌尔曼&#xff08;Bill Joy&#xff09;于1976年开发的&#xff0c;最初是为了在Unix系统上进行文本编…

技术前沿 |【大模型LLaMA:技术原理、优势特点及应用前景探讨】

大模型LLaMA&#xff1a;技术原理、优势特点及应用前景探讨 一、引言二、大模型LLaMA的基本介绍三、大模型LLaMA的优势特点五、结论与展望 一、引言 随着人工智能技术的飞速发展&#xff0c;大模型已成为推动这一领域进步的重要力量。近年来&#xff0c;大模型LLaMA以其卓越的…

我和jetson-Nano的故事(11)——在ros中编译usb_cam

在jetson nano中编译旧版本的usb_cam 1. 问题背景1.1 jetson nano的软件环境版本 2.问题现象3.问题原因及解决办法 1. 问题背景 最近给jetson nano重装了系统之后&#xff0c;在编译今年人工智能ROS小车比赛用的工程包的时候&#xff0c;遇到一个问题迟迟没有解决&#xff0c;…