【蓝桥杯速成】| 6.背包问题(01版)

news2025/3/19 10:21:25

01 背包

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

接下来让我们从题目入手,看看这个背包到底是怎么个事


题目:携带研究材料

问题描述

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。 

小明的行李空间为 N,物品种类为M,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。

解题思路

二维数组版

第一次看到背包问题,我的想法是:从N开始,放一个东西进去就减对应空间,加上相应价值,每次放需要对所有物品做一次尝试,确定是最合适的再放进去,但总觉得这样想好像有漏洞,没有办法同时考虑到空间和价值,也不知道如何组织代码,但其实这个想法就比较趋近于暴力解法,一共就这么多种物品,每个只有选与不选两种情况,根据排列组合的原则,我们实际上也能得到答案,就是时间复杂度为O(2^n),非常低效的代码呢

但明确背包问题实际上是动态规划中的一类题目,那么它的每一个状态必然是和之前的状态相关的(或许是直接取之前的状态,或许是利用前一状态再加上些什么)

我们仍然可以从动态规划五部曲着手,慢慢思考:

1.确定dp数组含义

因为题目涉及到两个变量:背包空间和物品价值,

那么我们完全可以用一个二维数组对这两个变量进行同时考虑

dp[ i ][ j ]        i 表示物品,j 表示背包

物品有序号,价值,占用空间 3个属性,背包则只有背包容量一种属性

那么很显然,让j代表背包空间,那么i 具体表示什么呢?

我们最后要得到的一定是:装入背包里物品价值的最大总和,即dp [ M ][ N ]是价值

这个结果一点是从前状态延续过来的,那么结合之前做的走方格之类 题目中的二维图,

不难想到 i 具体来说应该表示:在 0~i 中,任取物品

所以我们再明确一下

vector<vector<int>> dp(M,vector<int>(N+1))

//从下标为[0-i]的物品里任意取,放进容量为j的背包,最大价值总和

2.确定遍历顺序

对 i 来说,那肯定是从0号到M-1号待选物品

对 j 来说,就是0到N的背包容量大小

for(int i=0;i<M;i++){

        for(int j=0;j<=N;j++){

but 请注意我们还没有初始化,所以这个遍历开始值应该待定,

等我们做完初始化再来修改一下!

3.确定递推公式

dp [ i ][ j ] 走到这一格,我们就有两个选择,选i号物品和不选i号物品

和不同路径中,是从上面走下来还是左边走过来一个意思

那么选和不选肯定就是两种方案,我们的dp要取最大值

dp [ i ][ j ] =max(选,不选)

不选i号物品,那么最大值肯定延续之前的取值 dp [ i-1 ][ j ]

选择i号物品,那么需要腾够位置,再加上这一个物品的价值 dp [ i-1 ][ j - weight[ i ] ]+value[ i ]

整合一下就是

dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

4.初始化

考虑带0的dp[ i ][ 0 ],不管你有啥宝贝,咱背包没位置啥也不能装了嘛,

所以第一列应该全部初始化为0

 for(int i=0;i<M;i++){

        dp[i][0]=0;

}

 列考虑完再想想行,第一行,对于0号物品,only one的情况下,应该可以直接给出dp值

能放进来,那这个背包就值这么多,

for(int j=weight[0];j<=N;j++){//从能放进来开始

        dp[0][j]=value[0];//后续就这个价值

}

ok,还记得遍历顺序需要改一下不?

结合之前经验,为了使每一格都能被推导出来,

我们的遍历顺序和初始化要打好配合,不能有漏洞,不然肯定会崩溃

从递推关系来看,我们推导每一个dp依靠的是上面一格和左上方的一格(看情况,并不确定,但方位绝对是)初始化已经对第一行第一列赋值,形成了半包围结构,

那么我们的i就从1开始,j从0开始即可,

那么外层循环和内层循环能否调换一下位置呢?

这里是可以的,因为这个半包围结构非常贴心的屏蔽了出错因素,

不会使我们在先遍历行,或者先遍历列的时候出现前面的元素还没填上的情况

此外,还可以在内循环里再加上

if (j < weight[i]) dp[i][j] = dp[i - 1][j];// 放不进来不要逞强

那么完整代码在下面可以看到哦


因为递推我们只是在某一格的时候需要使用先前的状态,但先前状态并不需要保留,

所以这个二维数组能否压缩变为一维数组呢?

答案是:当然可以啦!

接下来我们研究一下

一维数组版(滚动数组)

还是执行动态规划五部曲

1.确定dp数组含义

dp[ j ]就指的是背包容量为 j 时的最大价值

这个值会在遍历每一个物品时进行更新

2.确定递推公式

参照二维dp的推理

dp[ j ]=max(dp[j],dp[j-weight[i]]+value[i])

就只是除掉 i ,不把它体现在dp数组的索引上,改用的值还是用

dp[j]就是指不把第 i 个值放进去,那么保持不动

dp[j-weight[i]]+value[i]就是指放入第 i 个值,那么dp值应该发生改变

但是最后我们取最大值,故dp[ j ]要使用max函数在两个方案中选择最大的那一个

3.初始化

这里我们只需要确定dp[0]=0,背包没空肯定没价值啦

剩下的也可以一起初始化为0,只要不是一个其它正数值

因为其他值可以从dp[0]=0开始逐个往后推算得到,

如果初始化为一个正数值,不能确保在max函数选择时它不会脱颖而出

所以保险起见,全部为0是没有风险的

4.确定遍历顺序

同样,我们要用一层循环来遍历物品,一层循环遍历背包容量

根据二维数组的经验,应该写为

for(int i=0;i<M;i++){//遍历物品

        for(int j=0;j<=N;j++){//遍历背包

但是这样在一维数组中同样可行吗???

我们试验一趟看看

i=0时,

dp[0]=0,

dp[1]=dp[1-1]+15//放入物品0,价值为15

dp[2]=dp[2-1]+15=dp[1]+15=30//再放入物品0

这明显就不符合题目要求了,我们只有一个物品0

但这里的dp[2]使用了dp[1]的结果,也就是放了两个物品0

也就是说正推会导致多次使用同一个物品

那我们试试逆推嘞!

假设背包最大容量就是2

dp[2]=dp[2-1]+15=dp[1]+15=0+15=15//dp[1]被初始化时就是0,背包容量为2时,加入一个0号,现在最大价值为15

dp[1]=dp[1-1]+15=dp[0]+15=15//背包容量为1时,加入一个0号,价值15

这样就完全符合规则啦!

所以我们应该将遍历顺序改为

for(int i=0;i<M;i++){//遍历物品

        for(int j=N;j>=weight[i];j--){//遍历背包,如果当前背包容量不够放入当前物品就停止

那么还有一个问题,就是这种情况下,内外层循环还能换位置吗??

倒序遍历背包大小,在每一种情况下遍历物品,这样的话就只能选择一个物品放入,显然是不符合题意的! 

 同样整合一下就是完整代码啦!可以参考下方给出的全部代码

我认为一维数组的方式更贴近我们往背包里放东西,容量减少,价值升高这个实际动作,

每次遍历就是在想包还有这么大,这个东西装不装

code

二维数组版

#include <bits/stdc++.h>
using namespace std;
int main() {
  int M, N;
  cin >> M >> N;
  vector<int> weight(M,0);
  vector<int> value(M,0);
  for(int i=0;i<M;i++){
    cin>>weight[i];
  }
  for(int i=0;i<M;i++){
    cin>>value[i];
  }
  vector<vector<int>> dp(M,vector<int>(N+1));
  for(int i=0;i<M;i++){
      dp[i][0]=0;
  }
  for(int j=weight[0];j<=N;j++){
      dp[0][j]=value[0];
  }
  for(int i=1;i<M;i++){
      for(int j=0;j<=N;j++){
          if(j<weight[i]) dp[i][j]=dp[i-1][j];
          else{
              dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
          }
      }
  }
    cout<<dp[M-1][N];
}

 一维数组版

#include <bits/stdc++.h>
using namespace std;
int main() {
  int M, N;
  cin >> M >> N;
  vector<int> weight(M,0);
  vector<int> value(M,0);
  for(int i=0;i<M;i++){
    cin>>weight[i];
  }
  for(int i=0;i<M;i++){
    cin>>value[i];
  }

  vector<int> dp(N+1,0);
  
  for(int i=0;i<M;i++){
    for(int j=N;j>=weight[i];j--){
      dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
    }
  }
  cout<<dp[N];
}

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

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

相关文章

【含文档+PPT+源码】基于小程序的智能停车管理系统设计与开发

项目介绍 本课程演示的是一款基于小程序的智能停车管理系统设计与开发&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系统 3…

idea 编译打包nacos2.0.3源码,生成可执行jar 包常见问题

目录 问题1 问题2 问题3 问题4 简单记录一下nacos2.0.3&#xff0c;编译打包的步骤&#xff0c;首先下载源码&#xff0c;免积分下载&#xff1a; nacos源码&#xff1a; https://download.csdn.net/download/fyihdg/90461118 protoc 安装包 https://download.csdn.net…

YOLOv8 OBB 旋转目标检测模型详解与实践

引言 在计算机视觉领域&#xff0c;目标检测是至关重要的任务之一。YOLO&#xff08;You Only Look Once&#xff09;系列算法因其高效性和准确性而广受欢迎。YOLOv8 作为稳定版本&#xff0c;在目标检测领域取得了显著成果&#xff0c;依旧能打。本文将深入探讨 YOLOv8 OBB&a…

机器学习之支持向量机(SVM)算法详解

文章目录 引言一、 什么是支持向量机&#xff08;SVM&#xff09;二、 SVM的基本原理三、数学推导1.线性可分情况2. 非线性可分情况3. 核函数 四、SVM的优缺点优点&#xff1a;缺点&#xff1a; 五、 应用场景六、 Python实现示例七、 总结 引言 支持向量机&#xff08;Suppor…

Linux系统移植篇(十一)Linux 内核启动流程

要分析 Linux 启动流程&#xff0c;同样需要先编译一下 Linux 源码&#xff0c;因为有很多文件是需要编译才 会生成的。首先分析 Linux 内核的连接脚本文件 arch/arm/kernel/vmlinux.lds&#xff0c;通过链接脚本可以 找到 Linux 内核的第一行程序是从哪里执行的。vmlinux.lds …

功能强大的电脑硬件检测及驱动安装工具

今天给大家带来一款超实用的电脑硬件检测软件——入梦工具箱。它是开源的&#xff0c;全程无广告干扰&#xff0c;使用起来清爽又安心。 打开入梦工具箱&#xff0c;进入“硬件信息”选项&#xff0c;电脑各个配件的详细参数一目了然。 无论是CPU的型号、频率&#xff0c;还是…

【Altium Designer】设计技巧

目录 技巧一&#xff1a;铺铜连接方式 技巧二&#xff1a;铺铜连接方式 技巧一&#xff1a;铺铜连接方式 Design -> Rules -> PolygonConnect 三种选择&#xff1a; 1&#xff09;Relief Connect&#xff1a;十字连接&#xff0c;可选择是45还是90 2&#xff09;Direct…

基于FPGA的3U机箱模拟量高速采样板ADI板卡,应用于轨道交通/电力储能等

板卡简介&#xff1a; 本板为模拟量高速采样板&#xff08;ADI&#xff09;&#xff0c;主要用于电机转速和相电流检测&#xff0c;以实现电机闭环控制。 性能规格&#xff1a; 电源&#xff1a;DC5V&#xff0c;DC3.3V&#xff0c;DC15V&#xff0c;DC24V FPGA&#xff1a;…

6. 顺序表和链表*****

目录 1. 顺序表 1.1 原理 1.2 常见的增删查改 1.3 顺序表的问题 2. 链表 2.1 原理 2.2 无头单向非循环的增删查改 2.3 链表面试题 1. 删除链表中等于给定值val的所有节点203. 移除链表元素 2. 链表逆置206. 反转链表&#xff08;考的最多&#xff09; 3.给你单链表的…

【AVRCP】蓝牙协议栈深度解析:AVCTP互操作性核心机制与实现细节

目录 一、事务标签&#xff08;Transaction Label&#xff09;机制 1.1 事务标签核心规则 1.2 事务标签作用域与并发性 1.3 实现建议与陷阱规避 1.4 协议设计思考 1.5 调试与验证 二、消息分片&#xff08;Fragmentation&#xff09;机制 2.1 分片触发条件 2.2 分片支…

【MySQL】基本查询(下)

文章目录 1.筛选分页结果2.Update3.Delete4.截断表5.插入查询结果6.聚合函数6.1什么是聚合函数6.2常见的聚合函数 7.group by7.1如何显示每个部门的平均工资和最高工资7.2显示每个部门的每种岗位的平均工资和最低工资7.3显示平均工资低于2000的部门和它的平均工资 8.总结 1.筛选…

Xpath Helper 替代 - XPath 测试器

Xpath Helper 最近开始&#xff08;2025.03&#xff09;无法使用了&#xff0c;选找了几款替代品&#xff0c;XPath 测试器 是目前看来最好的。 XPath 测试器 市场地址&#xff1a; https://chromewebstore.google.com/detail/xpath-测试器/cneomjecgakdfoeehmmmoiklncdiodmh …

C++学习之云盘项目nginx

1.复习 2.知识点概述 1. 一些基本概念 1.1 Nginx 初步认识 1.2 正向 / 反向代理 1.3 域名和 IP 2. Nginx 安装和配置 2.1 安装 2.2 配置 3. Nginx 的使用 3.1 部署静态网页 3.2 反向代理和负载均衡 课外知识导读 1. URL 和 URI 2. DNS 解析过程 1. 一些基…

JAVA学习*抽象类

抽象类 在Java中&#xff0c;被abstract关键字修饰的类被称为抽象类。 特点 1、当一个类继承了抽象类&#xff0c;一定要重写抽象方法&#xff01;&#xff01;&#xff01; public abstract class Animal {public int age;public String name;//抽象方法public abstract v…

数据库管理-第303期 数据库相关硬件文章汇总(20250319)

数据库管理303期 2025-03-19 数据库管理-第303期 数据库相关硬件文章汇总&#xff08;20250319&#xff09;1 CPU & 内存2 SSD3 RDMA4 存储5 CXL6 硬件采购7 数据库一体机总结 数据库管理-第303期 数据库相关硬件文章汇总&#xff08;20250319&#xff09; 作者&#xff1…

OctoTools:一个具有复杂推理可扩展工具的智体框架

25年2月来自斯坦福大学的论文“OctoTools: An Agentic Framework with Extensible Tools for Complex Reasoning”。 解决复杂的推理任务可能涉及视觉理解、域知识检索、数值计算和多步骤推理。现有方法使用外部工具增强大语言模型 (LLM)&#xff0c;但仅限于专业领域、有限的…

Canary

定义&#xff1a; Canary是一种用以防护栈溢出的保护机制。 原理&#xff1a; 是在一个函数的入口处&#xff0c;先从fs/gs寄存器中取出一个4字节&#xff08;eax,四字节通常是32位的文件&#xff09;或者8字节&#xff08;rax&#xff0c;通常是64位的文件&#xff09;的值…

c++领域展开第十七幕——STL(vector容器的模拟实现以及迭代器失效问题)超详细!!!!

文章目录 前言vector——基本模型vector——迭代器模拟实现vector——容量函数以及push_back、pop_backvector——默认成员函数vector——运算符重载vector——插入和删除函数vector——实现过程的问题迭代器失效memcpy的浅拷贝问题 总结 前言 上篇博客我们已经详细介绍了vecto…