背包问题(01背包及其优化(滚动数组和逆序枚举))

news2025/1/16 17:40:50

        终于是完结了AC自动机,接下来开个新坑——背包问题,背包的种类还是很多的,之前有学过,但都是这里看一点,那里看一点,导致现在都搞混了,所以重新系统看看这方面的内容。

        先从简单的入手——01背包,那么为什么叫01背包呢?我的理解是01背包其实就是有没有的问题,所以01就代表了两种状态,取或不取。01背包适用于每种物品只有一件的情况,也是最简单的,但是会碰到各种奇怪的“WA法”,所以需要我们进行优化。

        先看例题:

https://www.luogu.com.cn/problem/P2871icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P2871

题目描述

Bessie has gone to the mall's jewelry store and spies a charm bracelet. Of course, she'd like to fill it with the best charms possible from the N (1 ≤ N ≤ 3,402) available charms. Each charm i in the supplied list has a weight Wi (1 ≤ Wi ≤ 400), a 'desirability' factor Di (1 ≤ Di ≤ 100), and can be used at most once. Bessie can only support a charm bracelet whose weight is no more than M (1 ≤ M ≤ 12,880).

Given that weight limit as a constraint and a list of the charms with their weights and desirability rating, deduce the maximum possible sum of ratings.

输入格式

* Line 1: Two space-separated integers: N and M

* Lines 2..N+1: Line i+1 describes charm i with two space-separated integers: Wi and Di

输出格式

* Line 1: A single integer that is the greatest sum of charm desirabilities that can be achieved given the weight constraints

输入输出样例

输入 #1

4 6
1 4
2 6
3 12
2 7

输出 #1

23

        从题目我们就不难看出需要我们进行决策,要在有限的空间中装价值最大的情况,其实就是拿还是不拿的问题,我们需要同时维护容量和价值两个约束条件,我们不妨开个二维数组,是一个关于物品数量和最大容量的数组。

        在拿物品的时候,我们需要考虑两种情况:

        一:当我们的下一个物品的重量超过了我们背包的极限时,我们无法将其带上,所以面对此物品是我们的决策应该于上一个物品是一样的,即dp[i][j] = dp[i - 1][j]。

        二:当我们能带上某件物品时我们需要决策一下这件物品值不值得我们拿,因此,我们需要比较一下拿上它的价值和不拿的价值,也就是dp[i - 1][j]和dp[i - 1][j - w[i]] + v[i]哪个大我们就用其去更新数组。

        因此,代码如下:

 

#include <bits/stdc++.h>

using namespace std;

const int N = 13000;
const int M = 3410;

int w[N], v[N];
int dp[M][N];
int n ,m;

int main()
{
	cin >> n >> m;
	
	for(int i = 1; i <= n; i ++)
		cin >> w[i] >> v[i];
	
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= m; j ++)
		{
			if(j < w[i])
				dp[i][j] = dp[i - 1][j];
			else
				dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
		}	
		
	cout << dp[n][m] << endl;
}

        当我们高高兴兴去交一发的时候,MLE了!看一下空间复杂度O(nm),确实有可能会MLE,那么如何改进呢?

        相信有些同学看似会了,但是这个dp数组究竟是怎么操作的你真的明白吗?(其实一开始我也一直没弄懂,所以感觉总是学不进去)同时,想要优化空间,必须先弄懂dp数组的功能。

        我们从i = 1开始,i代表了什么呢?i代表了行,第i行意味着当我们的背包里有i个东西时我们的决策,j是一项约束,是从1到我们背包的最大容量,一开始i = 1;j = 1;就是代表了当背包里只能有一个物品,同时容量最大为1时,我们背包里所能有的最大价值,接着j渐渐变大,也就是在受到物品限制1个的情况下容量渐渐变大,我们所能获得的最大价值,由于我们先前初始化初态均为0,我们遵循二循环的操作,不断通过dp[0]行进行更新。不断得到新的决策,保证了我们容量不断变大时记录下真正的最大价值。

        而后,i ++,当背包物品约束变为两个时,容量依然从1开始慢慢变大,那么对于第二行来说,我们的基准态就是上一行记录下的dp[1]的价值,保证了我们的决策的正确性。

        以此类推,我们不难看出,每次我们的基准态都是上一次循环的内容,而更早的内容就成了占位置的僵尸数据,因此我们想,那我们用一个一维数组存储基准态,下一次我们直接将其更新,不就不会有僵尸数据了吗?

        理论可行,实践:

#include <bits/stdc++.h>

using namespace std;

const int N = 13000;
const int M = 3410;

int w[N], v[N];
int dp[N];
int n ,m;

int main()
{
	cin >> n >> m;
	
	for(int i = 1; i <= n; i ++)
		cin >> w[i] >> v[i];
	
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= m; j ++)
		{
			if(j < w[i])
				dp[j] = dp[j];
			else
				dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
		}	
		
	cout << dp[n][m] << endl;
}

        但是红惨了,为什么呢?

        我们发现,在更新新基准态的时候,我们是从前向后更新的,从而导致了有可能dp[j - w[i]]会被更新,但是我们后面的数据需要原来的基准数据,导致错误,既然从前往后不行,那么从后往前不就避免了这个问题了吗,因为后面的数据与前面不会有挂钩。

        实践代码:

 

#include <bits/stdc++.h>

using namespace std;

const int N = 13000;
const int M = 3410;

int w[N], v[N];
int dp[N];
int n ,m;

int main()
{
	cin >> n >> m;
	
	for(int i = 1; i <= n; i ++)
		cin >> w[i] >> v[i];
	
	for(int i = 1; i <= n; i ++)
		for(int j = m; j >= 1; j --)
		{
			if(j < w[i])
				dp[j] = dp[j];
			else
				dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
		}	
		
	cout << dp[m] << endl;
}

        这样就可以了!这就是逆序枚举的魅力。

        关于滚动数组,其实这种一维数组也就是滚动数组,但从更直观的角度,我们可有开一个dp[2][m]的数组,这样就不怕新数据覆盖旧值的情况出现了。

        其实上面的AC代码还能继续简化,我们可以将j < w[i]条件放到for中,从elsed语句的角度出发,就是防止j - w[i]出现负数的情况。

        最简代码如下:

#include <bits/stdc++.h>

using namespace std;

const int N = 13000;
const int M = 3410;

int w[N], v[N];
int dp[N];
int n ,m;

int main()
{
	cin >> n >> m;
	
	for(int i = 1; i <= n; i ++)
		cin >> w[i] >> v[i];
	
	for(int i = 1; i <= n; i ++)
		for(int j = m; j >= w[i]; j --)
				dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
		
	cout << dp[m] << endl;
}

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

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

相关文章

如何在 Java 中使用 JOptionPane 显示消息对话框

在 Java 开发中&#xff0c;JOptionPane 是一个非常实用的类&#xff0c;可以用来显示各种类型的对话框&#xff0c;例如信息对话框、警告对话框、错误对话框等。今天&#xff0c;我们将深入探讨如何使用 JOptionPane.showMessageDialog 方法来显示消息对话框&#xff0c;以及如…

面试被问准备多久要孩子?这样回答

听说有人面试被问到多久要孩子的问题&#xff0c;当时觉得很尴尬&#xff0c;不知如何回答&#xff0c;怕回答的不好不被录用&#xff0c;其实你可以这样回答&#xff0c;让面试官心满意足。 A 面试官&#xff1a;结婚了吗&#xff1f; 我&#xff1a;结婚了 面试官&#xff1…

MySQL—函数—数值函数(基础)

一、引言 首先了解一下常见的数值函数哪些&#xff1f;并且直到它们的作用&#xff0c;并且演示这些函数的使用。 二、数值函数 常见的数值函数如下&#xff1a; 注意&#xff1a; 1、ceil(x)、floor(x) &#xff1a;向上、向下取整。 2、mod(x,y)&#xff1a;模运算&#x…

Linux学习笔记9

Linux 进程间通信 介绍一下管道&#xff0c;管道是一种特殊的文件&#xff0c;它通过文件描述符来进行访问和操作 管道的读写操作是阻塞式的&#xff0c;如果没有数据可读&#xff0c;读操作会被阻塞&#xff0c;直到有数据可读&#xff1b;如果管道已满&#xff0c;写操作也…

Transformer 论文重点

摘要 提出了一个 Transformer 模型&#xff0c;针对于一个机器翻译的小任务上表现结果比当时所有模型的效果都好&#xff0c;并且架构相比其它更加简单&#xff0c;后面就火到了发现什么方向都能用的地步。 介绍 循环神经网络&#xff0c;特别是长短时记忆[ 13 ]和门控循环[…

计算机SCI期刊,中科院3区,专业性强,审稿专业

一、期刊名称 Frontiers in Neurorobotics 二、期刊简介概况 期刊类型&#xff1a;SCI 学科领域&#xff1a;计算机科学 影响因子&#xff1a;3.1 中科院分区&#xff1a;3区 三、期刊征稿范围 神经机器人前沿在体现自主系统的科学和技术及其应用方面发表了严格的同行评审…

解决Mac无法上网/网络异常的方法,重置网络

解放方法 1、前往文件夹&#xff1a;/Library/Preferences/SystemConfiguration 2 、在弹窗中输入上边的地址 3 、把文件夹中除了下图未选中的文件全部删掉&#xff0c;删除时需要输入密码 4 、重启mac 电脑就搞定了。

Windows 宿主机访问 VirtualBox 虚拟机中创建的 docker 容器中的 mysql8.0 的数据

一、场景需求 在开发环境中&#xff0c;一般使用 windows 系统进行开发&#xff0c;但需要在 linux 系统中创建运行 mysql8.0 的 docker 容器中进行测试&#xff08;win10特定版本或win11才能安装 docker&#xff09;&#xff0c;为了方便还需要在 windows 系统中通过 SQLyog …

操作系统笔记(1)进程相关

进程概念&#xff1a; 进程同步&#xff1a;多个相关进程在执行次序上进行协调&#xff0c;使并发执行的进程之间能按照一定的规则共享系统资源&#xff0c;并能很好的合作&#xff0c;从而使进程的执行具有可再现性。 进程之间可能存在互斥或者同步的关系。 互斥(间接相互制…

Android gradle kts 8.0以上版本配置签名和修改APK输出名字

目录 概述修改签名配置新建签名文件目录配置签名信息使用签名信息打包 修改APK名称 概述 之前写过一篇文章是通过Kotlin的Dsl结合gradle编写的插件来管理项目依赖&#xff0c;我是从一个开源项目叫DanDanPlayAndroid项目上学到的&#xff0c;那时还没有使用toml文件来管理项目…

模糊小波神经网络(MATLAB 2018)

模糊系统是一种基于知识或规则的控制系统&#xff0c;从属于智能控制&#xff0c;通过简化系统的复杂性&#xff0c;利用控制法来描述系统变量之间的关系&#xff0c;采用语言式的模糊变量来描述系统&#xff0c;不必对被控对象建立完整的数学模型。相比较传统控制策略&#xf…

【Git】分支管理 -- 详解

一、理解分支 分支就是科幻电影里面的平行宇宙&#xff0c;当你正在电脑前努力学习 C 的时候&#xff0c;另一个你正在另一个平行宇宙里努力学习 JAVA。 如果两个平行宇宙互不干扰&#xff0c;那对现在的你也没啥影响。不过&#xff0c;在某个时间点&#xff0c;两个平行宇宙…

Jenkins流水线pipeline--基于上一章的工作流程

1流水线部署 1.流水线文本名Jenkinsfile,将流水线放入gitlab远程仓库代码里面 2构建参数 2pipeline脚本 Jenkinsfile文件内容 pipeline {agent anyenvironment {key"value"}stages {stage("拉取git仓库代码") {steps {deleteDir()checkout scmGit(branc…

小熊家务帮day5-day7 客户管理模块1 (小程序认证,手机验证码认证,账号密码认证,修改密码,找回密码等)

客户管理模块 1.认证模块1.1 认证方式介绍1.1.1 小程序认证1.1.2 手机验证码登录1.1.3 账号密码认证 1.2 小程序认证1.2.1 小程序申请1.2.2 创建客户后端工程jzo2o-customer1.2.3 开发部署前端1.2.4 小程序认证流程1.2.4.1 customer小程序认证接口设计Controller层Service层调用…

运营商卷大模型,云厂商霸主地位不保?

文&#xff5c;艺 思 编&#xff5c;王一粟 经过了2023年的小试牛刀&#xff0c;2024年&#xff0c;三大运营商带着大模型一路狂飙。 刚刚过去的5月&#xff0c;中国电信、中国移动、中国联通三大运营商集体完成了新一轮的大模型进化&#xff0c;特别是围绕大模型的研发与…

2023年计算机图形学课程知识总结

去年就该写的&#xff0c;但是去年这个时候太忙了。 就写来自己看看。留个记录留个念 文章目录 1. 图形&#xff0c;图像的定义2. 点阵、矢量3. 走样&#xff0c;反走样4. 字符裁剪精度&#xff08;1&#xff09; 串精度&#xff08;2&#xff09; 字符精度&#xff08;3&…

【ubuntu软件版本管理】利用update-alternatives管理ubuntu软件

​ 我们有的时候希望在安装了新软件之后保留旧版本的软件&#xff0c;比如希望保留旧版本的gcc&#xff0c;以防以前写的C编译出问题&#xff0c;这时候就需要版本管理软件update-alternatives。 ​ 在此之前我们需要先弄清楚&#xff0c;什么是ubuntu的软件&#xff1f;拿C源…

力扣 54.螺旋矩阵

题目描述&#xff1a; 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5]示例 2&#xff1a; 输入&#…

【宠粉赠书】大模型时代的网络安全:安恒“网安三剑客”实战指南

不知不觉中&#xff0c;小智的粉丝已经突破一万。为了回馈粉丝们的厚爱&#xff0c;今天小智给大家送上一套网络安全界的三宝书——安恒"网安三剑客"。下面我会详细给大家介绍这套图书&#xff0c;文末留有领取方式。 随着人工智能&#xff08;AI&#xff09;和大模型…

基于协同过滤算法的东北特产销售系统的设计

基于协同过滤算法的东北特产销售系统的设计 管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;基础数据管理&#xff0c;公告管理&#xff0c;新闻信息管理&#xff0c;商品管理 农户账户功能包括&#xff1a;系统首页&#xf…