蓝桥杯每日一题------背包问题(一)

背包问题

阅读小提示:这篇文章稍微有点长,希望可以对背包问题进行系统详细的讲解,在看的过程中如果有任何疑问请在评论区里指出。因为篇幅过长也可以进行选择性阅读,读取自己想要的那一部分即可。

前言

背包问题可以看作动态规划系列入门的一个开端,欢迎开启动态规划之旅,在正式学习之前,我想说的是,动态规划真的不难,与贪心算法比较,动态规划有自己的多种板子,也有自己的多种套路;与高级数据结构比较,动态规划的代码量真的非常友好;与字符串类算法比较,动态规划没有那么抽象,ok话不多说,开始吧。
首先介绍一下动态规划的步骤(我自己总结的,自己用起来感觉还不错,y总也有介绍过闫式dp分析法,大家感兴趣可以看一看,怎么方便怎么来)
求解动态规划有两个大的阶段,分别是定义dp数组和推导状态转移方程。大家觉得这两个哪个重要呢?诚然状态转移方程是动态规划的关键,但是我在做题的过程中感受到当你的dp数组定义正确了,状态转移方程的推导就是自然而然的事情,所以对我来说,最关键的是定义dp数组。我们可以按照下面的步骤定义dp数组。
第一步:缩小规模。大家在大学学到动态规划时,一般都会拿来和贪心比,和分治比,无论哪一个我们都不能一口吃个胖子,都是从最基础的那个地方开始,一步一步往下走,最终走到终点。既然要缩小规模,那必然要有一个维度来定义当前的规模,放在背包问题里,规模就是考虑的物品的个数,那么用一个维度就可以了,放在区间dp里,规模是区间的大小,而不同的区间结果是不一样的,所以需要两个维度来表示区间的左右端点。
第二步:限制。放在背包问题里,限制就是背包的容量,你选的物品的总体积不能超过当前背包容量,所以你需要一个维度来表示当前的体积。
第三步:写出dp数组。走到这里,根据规模和限制定义了dp数组,dp[i][j]表示当前考虑了前i个物品,背包容量为j时能够装的最大价值。我们求的就是最大价值,那么dp数组对应的值就是最大价值,一般和所求是一样的,求什么就记录什么。
第四步:修改dp数组。这一步就是在写状态转移方程时,你发现定义的dp数组维度少了,还需要其它信息,那么这个时候就是需要什么往dp数组里面加什么,即增加维度,但是要注意一点,一般dp数组的维度和时间复杂度是正相关的,维度过多,很有可能超时。

01背包

在这里插入图片描述
定义dp数组
第一步:缩小规模。考虑n个物品,那我就先考虑1个物品,在考虑2个物品…,需要一个维度表示当前考虑的物品个数。
第二步:限制。所选物品个数不能超过物品容量,那么需要一个维度记录当前背包的容量。
第三步:写出dp数组。dp[i][j]表示当前考虑了前i个物品,背包容量为j时的最大价值。
第四步:推状态转移方程。dp[i][j]应该从哪里转移过来呢,必然是从前i-1个物品转移,我要考虑两种情况,对于第i个物品,可以选择要它,也可以不要它,如果要第i个物品,我就需要背包里面给我预留出第i个物品的体积,也就是从a[i-1][j-v[i]]转移,同时也能获得该物品的价值。如果不要第i个物品,那么之前从前一个状态相同容量的背包转移过来就行,即a[i-1][j]。
综上状态转移方程如下
a[i][j] = max(a[i-1][j],a[i-1][j-v[i]]+w[i])
考虑写代码了
第一步:确定好遍历顺序,对于背包问题,一般第一个for遍历规模,第二个for遍历限制。

for(int i = 1;i <= n;i++) {for(int j = 1;j <= m;j++) {dp[i][j] = dp[i-1][j];//为什么要在这里转移,因为这个转移是一定会发生的,而另一个转移不一定会发生if(j>=v[i])dp[i][j] = Math.max(dp[i-1][j-v[i]]+w[i], dp[i][j]);}}

第二步:考虑是否要对dp数组初始化,这里不需要,因为最开始的状态考虑前0个物体,它的值就是0,不需要管。
全部代码如下,

import java.util.Scanner;
public class Main {
public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();int V = scanner.nextInt();int[] v = new int[n+1];int[] w = new int[n+1];for (int i = 1; i < w.length; i++) {v[i] = scanner.nextInt();w[i] = scanner.nextInt();}int[][] dp = new int[n+1][V+1];
//	for (int i = 0; i < dp.length; i++) {
//		dp[0][i] = 1;
//	}for (int i = 1; i < dp.length; i++) {for (int j = 0; j < V+1; j++) {dp[i][j] = Math.max(dp[i][j], dp[i-1][j]);if(v[i]<=j) {dp[i][j] = Math.max(dp[i][j], dp[i-1][j-v[i]]+w[i]);}}}System.out.println(dp[n][V]);
}
}

考虑对dp数组进行维度优化,这里的优化并不会降低它的时间复杂度,但是可以减低空间复杂度,提高空间利用率,并且它也可以算是滚动dp的一个因子,而且里面有一个思想在后续做题的过程中也需会用到!
我们考虑一下在转移的过程中我只用了a[i]和a[i-1]对于a[i-2],a[i-3]我后续都用不到了,所以没有必要存它,考虑如果我只用一个一维的dp,思路还是一样的,但是代码该怎么写。
令dp[i]表示背包容量为i时最多能容纳的物品价值。自己尝试把代码里表示物品个数的那一维删掉,就成了

import java.util.Scanner;
public class Main {
public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();int V = scanner.nextInt();int[] v = new int[n+1];int[] w = new int[n+1];for (int i = 1; i < w.length; i++) {v[i] = scanner.nextInt();w[i] = scanner.nextInt();}int[] dp = new int[V+1];for (int i = 1; i < dp.length; i++) {for (int j = 0; j < V+1; j++) {//dp[j] = Math.max(dp[j], dp[j]);if(v[i]<=j) {dp[j] = Math.max(dp[j], dp[j-v[i]]+w[i]);}}}System.out.println(dp[V]);
}
}

直接这样提交可以过吗?当然不可以,我们还记得我们的题目是每个物品只有一个吗?我们分析一下dp[j] = Math.max(dp[j], dp[j-v[i]]+w[i]);
假设当前遍历到了i=5,假设j=5时,dp[j]=dp[j-v[i]]+w[i].说明此时我们拿了第5个物品,当遍历到j=10时假设此时v[i]=5,dp[10]=dp[10-5]+w[i]=dp[5]+w[i],可以看见dp[10]是从dp[5]转移的,但是我们的本意是不是dp[5]表示的应该是i=4时的结果,但是刚刚我们也看见了,遍历到dp[10]时,dp[5]已经被更新了,它不是i=4时的dp[5],所以会出错。好,我们再深究一下,出错的结果是啥?dp[5]是不是已经选了物品5了?此时dp[10]==dp[5]+w[i]又选了一次物品5,说明物品5被选了多次,而题目要求每个物品只能选一次,所以不符合题意。如果改一改,改成每个物品可以选无数次,那么这里就是没有问题,记住这一点。
回到这个题目,那我们应该怎么改,在求dp[10]时,会用到dp[5],归纳一下,在求dp[i]时,会用到dpj,我们在遍历到i之前不能动dp[j]。也就是说,先遍历大的数,所以我们直接倒序遍历就行了。来看代码吧,

	for (int j = 0; j < n; j++) {for (int i = k; i >= v[j]; i--) {//i<v[j]时不能转移,所以直接遍历到v[j]就行,这样后面就不用if语句判断是否能转移了。dp[i] = Math.max(dp[i], dp[i - v[j]] + w[j]);}}

全部代码

import java.io.IOException;
import java.util.Scanner;
public class Main {public static void main(String[] args) throws IOException {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();int k = scanner.nextInt();int[] v = new int[n];int[] w = new int[n];for (int i = 0; i < n; i++) {v[i] = scanner.nextInt();w[i] = scanner.nextInt();}int[] dp = new int[k + 1];for (int j = 0; j < n; j++) {for (int i = k; i >= v[j]; i--) {// System.out.println("---");dp[i] = Math.max(dp[i], dp[i - v[j]] + w[j]);}}System.out.println(dp[k]);}
}

借此机会,再讲一下滚动dp,他不算是单独的一种dp,只是对dp的一种空间优化方法,防止爆内存。刚刚讲过,在dp数组遍历的过程中我只用到了当前为i时的状态和前一个为i-1时的状态,其它的都不要了,所以其实我可以把dp[n+1][V+1],变成dp[2][V+1],如果dp[0][V+1]表示考虑了前0个物品的状态,遍历到i=1时,用dp[1][V+1]表示考虑了前1个物品的状态,遍历到i=2时,前0个物品的状态我不需要记录了,此时可以拿dp[0][V+1]表示考虑了前2个物品的状态,如此循环往复。可以发现这是交替使用的,那么数字里面什么是交替出现的?奇偶数呀,所以可以用奇偶数来判断,如dp[i&1][j]和dp[(i-1)&1][j]。在使用滚动dp时,其实修改很好修改,只要在你原来的代码里,注意是使用二维数组的那个代码哈,把dp[i][j]和dp[i-1][j]改成dp[i&1][j]和dp[(i-1)&1][j]就行了。因此它也不易出错,比起刚刚介绍的直接把dp数组减少一维。看代码吧。

import java.io.IOException;
import java.util.Scanner;
public class Main {public static void main(String[] args) throws IOException {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();int k = scanner.nextInt();int[] v = new int[n + 1];int[] w = new int[n + 1];for (int i = 1; i <= n; i++) {v[i] = scanner.nextInt();w[i] = scanner.nextInt();}int[][] dp = new int[2][k + 1];for (int i = 1; i <= n; i++) {for (int j = 0; j <= k; j++) {// System.out.println(i + " " + j + " ---------");if (j >= v[i]) {dp[i&1][j] = Math.max(dp[(i - 1)&1][j], dp[(i - 1)&1][j - v[i]] + w[i]);} else {// System.out.println(i + " " + j);dp[i&1][j] = dp[(i - 1)&1][j];}}}System.out.println(dp[n&1][k]);}
}

完全背包

在这里插入图片描述
完全背包和01背包的不同在于完全背包对每个物品的可选次数没有限制,那么在遍历的时候就会比原来多出一个维度,dp数组的定义还是一样的,dp[i][j]表示考虑前i个物品当前背包容量为j时的最大价值。那么可选物品不受限制如何体现呢?
01背包在递推dp数组时有两个嵌套for循环,第一层遍历当前考虑前i个物品,第二层遍历当前背包的容量为j,那么我们需要加入一个维度,这个维度表示选择j2个第i个物品,完整代码如下

import java.util.Scanner;
public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();int k = scanner.nextInt();int[] v = new int[n + 1];int[] w = new int[n + 1];for (int i = 1; i <= n; i++) {v[i] = scanner.nextInt();w[i] = scanner.nextInt();}int[][] dp = new int[n + 1][k + 1];for (int i = 1; i <= n; i++) {for (int j = 1; j < k + 1; j++) {for (int j2 = 0; j2 * v[i] <= j; j2++) {dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - j2 * v[i]] + j2 * w[i]);}}}System.out.println(dp[n][k]);}
}

此时的复杂度就是 O ( n 3 ) O(n^3) On3。我们来回顾一下,我们之前有没有类似的代码。在将01背包压缩成1维时,我们是不是有一种错误写法,第二维如果正序遍历会导致同一个物品被多次选择,这对于01背包来说是不合题意的,但是正好符合完全背包的要求,所以之前那个错误的代码完全可以用到完全背包上,并且这个的时间复杂度只需要 O ( n 2 ) O(n^2) On2,代码如下。

import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();int k = scanner.nextInt();int[] v = new int[n + 1];int[] w = new int[n + 1];for (int i = 1; i <= n; i++) {v[i] = scanner.nextInt();w[i] = scanner.nextInt();}int[] dp = new int[k + 1];for (int i = 1; i <= n; i++) {
//			for (int j = 0; j < dp.length && j >= v[i]; j++) {for (int j = v[i]; j < dp.length; j++) {
//				System.out.println(dp[i] + " " + (dp[j - v[i]] + w[i]) + " " + i + " " + j);dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);}}
//		for (int i = 0; i < dp.length; i++) {
//			System.out.print(dp[i] + " ");
//		}System.out.println(dp[k]);}
}

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

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

相关文章

CTFSHOW命令执行web入门29-54

description: >- 这里就记录一下ctfshow的刷题记录是web入门的命令执行专题里面的题目,他是有分类,并且覆盖也很广泛,所以就通过刷这个来,不过里面有一些脚本的题目发现我自己根本不会笑死。 如果还不怎么知道写题的话,可以去看我的gitbook,当然csdn我也转载了我自己的…

幻兽帕鲁服务器怎么更新?如何快速在腾讯云更新幻兽帕鲁Palworld服务器,显示版本不兼容怎么解决

幻兽帕鲁服务器怎么更新&#xff1f;如何快速在腾讯云更新幻兽帕鲁Palworld服务器&#xff0c;显示版本不兼容怎么解决。最近的幻兽帕鲁服务器又更新了。 如何在不需要远程登录服务器的情况下&#xff0c;通过一行命令来更新幻兽帕鲁呢&#xff1f; 腾讯云轻量云一键部署幻兽…

今年春节,德施曼成“春晚御用”智能锁,亮相总台春晚直播间

总台春晚&#xff0c;是每年春节期间的最大热点。 今年除夕夜&#xff0c;高端智能锁品牌德施曼&#xff0c;不仅成为“春晚御用”智能锁&#xff0c;还将旗下的哨兵猫眼智能锁&#xff0c;卖到了总台春晚的直播间里。龙年春节&#xff0c;德施曼智能锁携手小红书《大家的春晚》…

金融信贷风控评分卡模型

评分卡模型概念 评分模型是根据借款人的历史数据&#xff0c;选取不同维度的数据类型&#xff0c;通过计算而得出的对借款人信用情况打分的模型。不同等级的信用分数代表了借款人信用情况的好坏&#xff0c;以此来分析借款人按时还款的可能性。 评分卡模型分类 A卡&#xff…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 2月10日,星期六

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年2月10日 星期六 农历正月初一 春节 1、 国务院&#xff1a;到2025年&#xff0c;初步建成覆盖各领域、各环节的废弃物循环利用体系。 2、 国家移民管理局&#xff1a;部分国家人员可以用更多事由免签入境海南。 3、 市场…

css的布局(BFC)

一、css中常规的定位方案 1、普通流 元素按照其在HTML中的先后位置自上而下布局。 行内元素水平排列&#xff0c;当行被占满后换行&#xff1b;块级元素则会被渲染为完整的一行。 所有元素默认都是普通流定位。 2、浮动 元素首先按照普通流的位置出现&#xff0c; 然后根据浮动…

跟着cherno手搓游戏引擎【23】项目维护、2D引擎之前的一些准备

项目维护&#xff1a; 修改文件结构&#xff1a; 头文件自己改改就好了 创建2DRendererLayer&#xff1a; Sandbox2D.h: #pragma once #include "YOTO.h" class Sandbox2D :public YOTO::Layer {public:Sandbox2D();virtual ~Sandbox2D() default;virtual void O…

图神经网络与图表示学习: 从基础概念到前沿技术

目录 前言1 图的形式化定义和类型1.1 图的形式化定义1.2 图的类型 2 图表示学习2.1 DeepWalk: 融合语义相似性与图结构2.2 Node2Vec: 灵活调整随机游走策略2.3 LINE: 一阶与二阶邻接建模2.4 NetMF: 矩阵分解的可扩展图表示学习2.5 Metapath2Vec: 异构图的全面捕捉 3 图神经网络…

【正式】今年第一篇CSDN(纯技术教学)

一、文件上传简介 文件上传漏洞是指用户上传了一个可执行的脚本文件&#xff08;木马、病毒、恶意脚本、webshell等&#xff09;&#xff0c;并通过此脚本文件获得了执行服务器端命令的能力。上传点一般出现在头像、导入数据、上传压缩包等地方&#xff0c;由于程序对用户上传…

TOP100 二叉树(三)

11.114. 二叉树展开为链表 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺…

SegmentAnything官网demo使用vue+python实现

一、效果&准备工作 1.效果 没啥好说的&#xff0c;低质量复刻SAM官网 https://segment-anything.com/ 需要提一点&#xff1a;所有生成embedding和mask的操作都是python后端做的&#xff0c;计算mask不是onnxruntime-web实现的&#xff0c;前端只负责了把rle编码的mask解…

5G技术对物联网的影响

随着数字化转型的加速&#xff0c;5G技术作为通信领域的一次重大革新&#xff0c;正在对物联网&#xff08;IoT&#xff09;产生深远的影响。对于刚入行的朋友们来说&#xff0c;理解5G技术及其对物联网应用的意义&#xff0c;是把握行业发展趋势的关键。 让我们简单了解什么是…

巧用liteflow,告别if else,SpringBoot整合liteflow

假设有一个三个原子业务&#xff0c;吃饭、喝水、刷牙。 现在有三个场景&#xff0c;分别是 场景A: 吃饭->刷牙->喝水 官网地址&#xff1a;https://liteflow.cc/ 1.添加依赖&#xff1a; <dependency><groupId>com.yomahub</groupId><artifactI…

基于鲲鹏服务NodeJs安装

准备工作 查看当前环境 uname -a查看鲲鹏云CPU架构 cat /proc/cpuinfo# 查看CPU architecture项&#xff0c;8表示v8&#xff0c;7表示v7下载Node.js NodeJs 选择 Linux Binaries (ARM) ARMv8 wget -c https://nodejs.org/dist/v12.18.3/node-v12.18.3-linux-arm64.tar.xz…

基于完全二叉树实现线段树-- [爆竹声中一岁除,线段树下苦踌躇]

文章目录 一.完全二叉树完全二叉树的父子结点引索关系 二.线段树三.基于完全二叉树实现线段树关于线段树的结点数量问题的证明递归建树递归查询区间和递归单点修改线段树模板题 一.完全二叉树 完全二叉树的物理结构是线性表,逻辑结构是二叉树 完全二叉树的父子结点引索关系 …

OpenCV与机器学习:使用opencv和sklearn实现线性回归

前言 线性回归是一种统计分析方法&#xff0c;用于确定两种或两种以上变量之间相互依赖的定量关系。在统计学中&#xff0c;线性回归利用线性回归方程&#xff08;最小二乘函数&#xff09;对一个或多个自变量&#xff08;特征值&#xff09;和因变量&#xff08;目标值&#…

深度优先搜索(DFS)与广度优先搜索(BFS):探索图与树的算法

一、引言 在图论和树形结构中&#xff0c;搜索算法是寻找从起点到终点的路径的关键。其中&#xff0c;深度优先搜索&#xff08;DFS&#xff09;和广度优先搜索&#xff08;BFS&#xff09;是最常用且最基础的两种搜索算法。本文将详细介绍广度优先搜索&#xff08;BFS&#xf…

一文彻底搞懂Kafka如何保证消息不丢失

文章目录 1. kafka 架构2. producer端是如何保证数据不丢失的2.1 同步发送2.2 异步发送2.3 批量发送 3. consumer端是如何保证数据不丢失的3.1 手动提交3.2 幂等性消费 4. broker端是如何保证数据不丢失的4.1 副本机制4.2 ISR机制4.3 刷盘机制 1. kafka 架构 Producer&#xff…

ES6 ~ ES11 学习笔记

课程地址 ES6 let let 不能重复声明变量&#xff08;var 可以&#xff09; let a; let b, c, d; let e 100; let f 521, g "atguigu", h [];let 具有块级作用域&#xff0c;内层变量外层无法访问 let 不存在变量提升&#xff08;运行前收集变量和函数&#…

基于SpringBoot+Vue的服装销售商城系统

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是墨韵&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c;不进则退。学习如赶路&#xff0c;不能慢一步。 目录 一、项目简介 二、开发技术与环…