我在这里介绍4种常见的背包问题,这里我想按易 --> 难程度从01背包,完全背包,分组背包,多重背包的顺序介绍。(封面附在最后

一,01背包问题(后面三个背包问题的基础

01背包问题就是从 n 物品中选取若干个体积为 v[i] 价值为 w[i] 的物品(同一个物品不能重复选),使容量为 V 的背包的价值最大。

1,01背包二维模板代码

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

状态定义:我们定义 dp[i][j] 为只考虑前 i 物品时,j 容量能装下的物品价值总和的最大值

状态转移方程:dp[i][j] = max(dp[i][j], dp[i - 1][j - v] + w)

首先我们先继承上一个状态 dp[i][j] = dp[i - 1][j] ,然后判断是否满足状态转移的条件,即是否有空间加入当前的 i 物品,然后进行状态转移,其中状态转移的基础是 dp[i - 1][j - v] ,即不包含物品 n (只包含前 n - 1 个物品),空间为 j - v 的最大价值。


2,01背包一维模板代码

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

状态定义:我们定义 dp[j] 为只考虑前 i 个物品时,j 容量能装下的物品价值总和的最大值

状态转移方程:dp[j] = max(dp[j], dp[j - w] + v)

这里的一维模板代码是二维模板代码的空间优化,这里需要被理解的主要有两点,二维压缩为一维的合理性以及为什么要倒序遍历 j 。

1,二维压缩为一维的合理性

我们的最终目的是为了得出最大价值,对于二维代码来说就是 dp[n][V] ,那其他存储空间有什么用呢,从代码中我们我们不难看出我们在状态继承和状态转移中需要用到其他存储空间(dp[i][j]), 那么一维能满足这两点吗?

从状态继承来看,如果是一维,那么其实就不存在状态继承的问题,因为你在每个状态上的操作都在同一行(只有一行);

从状态转移来看,既然状态已经被继承了,那么我们就完全可以在被继承的状态上(旧状态)的基础上转移,这与二维背包的逻辑完全相同。

2,为什么要倒序遍历 j 

倒序遍历的根本原因在于01背包的特性:同一个物品不能重复选。

那换句话说,是不是如果不倒序遍历 j ,也就是正序遍历 j ,就会重复选同一个物品,答案是:是的。原因在于每个状态都是从 dp[j - w] 也就是前 w 个状态继承过来的,那如果先更新前面的再更新后面的,就会发生状态转移的基础 dp[j] 不再是(旧状态)而是已经更新过的新状态(这是一定的),外在表现就是重复选同一个物品(记住后面会考)。

二,完全背包问题

完全背包问题就是从 n 物品中每种选取若干个体积为 v[i] 价值为 w[i] 的物品(同一种物品能重复选且每种物品的数量无上限),使容量为 V 的背包的价值最大。

1,完全背包二维模板代码

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

状态定义:我们定义 dp[i][j] 为只考虑前 i 物品时,j 容量能装下的物品价值总和的最大值

状态转移方程:dp[i][j] = max(dp[i][j], dp[i][j - v] + w)

首先我们先继承上一个状态 dp[i][j] = dp[i - 1][j], 然后判断是否满足状态转移的条件,即是否有空间加入当前的 i 物品,然后进行状态转移,其中状态转移的基础是 dp[i][j - v] ,即包含物品 n (只包含前 n 种物品),空间为 j - v 的最大价值。

不难看出,完全背包模板代码与01背包模板代码的区别就在于状态转移的基础,这也取决于完全背包问题的特性:同一种物品能重复选且每种物品的数量无上限。

2,完全背包一维模板代码

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

状态定义:我们定义 dp[j] 为只考虑前 i 物品时,j 容量能装下的物品价值总和的最大值

状态转移方程:dp[j] = max(dp[j], dp[j - w] + v)

这里的一维模板代码是二维模板代码的空间优化,这里需要被理解的主要也有两点,二维压缩为一维的合理性以及为什么要正序遍历 j 。(其中二维压缩为一维的合理性与01背包问题一致,这里不再赘述,详见上文)

1,为什么要正序遍历 j 

正序遍历的根本原因也在于完全背包的特性:同一种物品能重复选且每种物品的数量无上限。

(详见01背包)

三,分组背包问题

分组背包问题就是从 n 物品中每组选取一个体积为 v[i][k] 价值为 w[i][k] 的物品(任意一组物品能且仅能入选其中一个物品),使容量为 V 的背包的价值最大。

(二维模板代码这里就不展示了,前面已经讲过二维压缩到一维的合理性和原理了,我们直接用最优的)

1,分组背包一维模板代码

/*题目描述(hdu 1712)
输入包含多个数据集。
每个数据集的第一行包含两个正整数 N 和 M,N 表示课程数量,M 表示 ACboy 可用的天数。
接下来是一个矩阵 A[i][j],(1 <= i <= N <= 100,1 <= j <= M <= 100)。
A[i][j] 表示如果 ACboy 在第 i 门课程上花费 j 天,他将获得价值为 A[i][j] 的收益。
当 N = 0 且 M = 0 时,输入结束。
*/
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
#define M 105//提交以前记得改数据
using ll = long long int;
using namespace std;int main() {int n, m;while (~scanf("%d %d", &n, &m) && n && m) {int w[M][M];int dp[M] = { 0 };int v[M];for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) {cin >> w[i][j];v[j] = j;}	for (int i = 1; i <= n; i++) for (int j = m; j >= 0; j--) //总天数for (int k = 1; k <= j; k++) //付出的天数(k只是一个编号)if (j >= v[k])dp[j] = max(dp[j], dp[j - v[k]] + w[i][k]);cout << dp[m] << endl;}return 0;
}
/*样例输入
2 2
1 2
1 3
2 2
2 1
2 1
2 3
3 2 1
3 2 1
0 0
*//*样例输出
3
4
6
*/

状态定义:我们定义 dp[j] 为只考虑前 i 物品时,j 容量能装下的物品价值总和的最大值

状态转移方程:dp[j] = max(dp[j], dp[j - v[k]] + w[i][k])

这里的一维模板代码是二维模板代码的空间优化,这里需要被理解的主要有三点:二维压缩为一维的合理性(这里不再赘述),为什么要倒序遍历 j 以及为什么要三层循环。

1,为什么要倒序遍历 j 

单从这一点来看分组背包一维代码和01背包一维代码类似,那是什么导致了这个 “类似” 呢?

我们还是从两种问题的特性出发:

01背包:同一个物品不能重复选

分组背包:任意一组物品能且仅能入选其中一个物品

大家应该发现了,“一个” 是关键,那为什么这一特性会导致倒序呢?

(详见01背包)

2,为什么要三层循环

这还是取决于分组背包的特性:每组一个。

既然每组只能选一个,我们又要价值最大,那我们就要在容量的范围内尽可能选最大的,怎么选最大呢,最简单的思路就是应用选择排序的原理:打擂台(价值更大的当擂主,然后一直守擂,知道遇到更大的挑战者),事实上我们在这里也是这么做的,而这一操作又自成一层循环,加上本来的两层,2+1=3。

四,多重背包

多重背包问题就是从 n 物品中每种选取若干个体积为 v 价值为 w 的物品(同一种物品能重复选,每种物品的数量有限),使容量为 V 的背包的价值最大。

思路1:

在展示最优代码之前我先说一种简单的方法,把多重背包问题看成是01背包问题,毕竟多重背包问题物品是有限的而且还没有选取限制,实际上01背包问题是特殊的多重背包问题(每种物品只有一个)。但是特殊情况的解法并不一定能适用于普遍情况,当每种问题的物品太多就会TLE,所以这种方法不推荐用,也就图一乐。

1,多重背包一维模板代码

#include<bits/stdc++.h>
using ll = long long int;
using namespace std;
#define M 100005
int dp[M] = { 0 };
int main() {int V, n;cin >> V >> n;for (int i = 0, v, w, s; i < n; i++) {cin >> v >> w >> s;for (int k = 1; s; s -= k, k *= 2) {//把 s 进行二进制拆分,类似于 st 表k = min(s, k);for (int j = V; j >= k * v; j--) dp[j] = max(dp[j], dp[j - k * v] + k * w);}}cout << dp[V];return 0;
}

思路2:

这个代码一看就知道我为什么要把它放到最后了,要弄懂这个代码需要一定的 st 表的基础,那这时候就有兄弟要问了,有没有不会 st 表也能理解的方法呢,有的兄弟有的,二进制大家都懂吧,任意一个数都能用二进制表示,也就是由1,2,4,8,16,32,64...这些数中的一部分组成的,那么我们还是用沿用思路1,只是在其中加一步

for (int k = 1; s; s -= k, k *= 2) {k = min(s, k);...
}

这一步其实就是把一种问题的所有物品进行整合,整合成 1 * v,2 * v,4 * v,8 * v,16 * v,32 * v,64 * v...,然后从 1 * v 这个部分开始倒序刷表(为什么倒序我就不赘述了),这样一来不管面对多大的体积 j,我们总有一种合适的组合方式适合 j 。这样一来我们只要预处理好 1 * v,2 * v,4 * v,8 * v,16 * v,32 * v,64 * v...我们就能以对数阶的复杂度进行刷表。

写完之后博主要燃尽了,给个赞回回血吧,我先吸吸猫(转载)

罗小黑乱入(都是猫猫为什么不可以)

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

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

相关文章

Leetcode 18 java

​​​​​1​​​​​​​141. 环形链表1 题目 ​​​​​1​​​​​​​141. 环形链表 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表…

Linux 正则表达式详解(基础 + 扩展 + 实操)

Linux 正则表达式详解&#xff08;基础 扩展 实操&#xff09; 正则表达式&#xff08;Regular Expression&#xff0c;简称 RE&#xff09;是 Linux 文本处理的核心工具&#xff0c;用于定义字符匹配模式&#xff0c;配合 grep、sed、awk 等工具可实现文本过滤、查找、替换等…

Json-rpc通信项目(基于C++ Jsoncpp muduo库)

一、介绍RPC RPC&#xff08;Remote Procedure Call&#xff09;远程过程调用&#xff0c;一种通过网络从远程计算器上请求服务&#xff0c;而不需要了解底层网络通信细节&#xff0c;RPC可以使用多种网络协议进行通信&#xff0c;并且在TCP/IP网络四层模型中跨越了传输层和应…

RL【9】:Policy Gradient

系列文章目录 Fundamental Tools RL【1】&#xff1a;Basic Concepts RL【2】&#xff1a;Bellman Equation RL【3】&#xff1a;Bellman Optimality Equation Algorithm RL【4】&#xff1a;Value Iteration and Policy Iteration RL【5】&#xff1a;Monte Carlo Learnin…

Redis是什么?一篇讲透它的定位、特点与应用场景

Redis是什么&#xff1f;一篇讲透它的定位、特点与应用场景 1. Redis的定义与核心概念 1.1 什么是Redis&#xff1f; Redis&#xff08;Remote Dictionary Server&#xff09; 是一个开源的、基于内存的数据结构存储系统&#xff0c;可以用作数据库、缓存和消息代理。Redis由…

一款免费开源轻量的漏洞情报系统 | 漏洞情报包含:组件漏洞 + 软件漏洞 + 系统漏洞

工具介绍 bug_search一款免费开源轻量的漏洞情报系统 基于python3 Amis2.9 开发&#xff0c;仅依赖Flask,requests&#xff0c;无需数据库&#xff0c;Amis是百度开源的低代码前端框架漏洞情报包含&#xff1a;组件漏洞 软件漏洞 系统漏洞 增加邮件发送消息报警功能增加钉钉…

详解在Windows系统中生成ssl证书,实现nginx配置https的方法

目录一、下载安装OpenSSL二、证书生成三、修改nginx配置总结Nginx 是一个高性能的HTTP和反向代理web服务器&#xff0c;在进行web项目开发时&#xff0c;大多都是使用nginx对外提供web服务。HTTPS &#xff08;全称&#xff1a;Hypertext Transfer Protocol Secure [5]&#xf…

AI视觉算法中的OpenCV API (二)

视频写入 (FourCC, VideoWriter)​ 1. VideoWriter_fourcc - 视频编码器四字符代码 # OpenCV 3.x, 4.x fourcc cv2.VideoWriter_fourcc(M,J,P,G)fourcc cv2.VideoWriter_fourcc(*H264)fourcc cv2.VideoWriter_fourcc(*MJPG) ​FourCC​&#xff1a; 代表 ​Four ​Charac…

分享| 2025年版AIGC数字人实验室解决方案教学资源解析

AIGC数字人实验室解决方案构建了涵盖基础层、平台环境层与资源层的多层次教学架构&#xff0c;依托150平方米的实体空间与60人并行授课的规模化支持&#xff0c;为学生提供了技术实践与创新的高效平台。其教学资源体系覆盖AIGC文本生成、图像生成、数字人应用与智能体开发四大核…

内存大(巨)页

一、大&#xff08;巨&#xff09;页 大&#xff08;巨&#xff09;页&#xff0c;很好理解&#xff0c;就是的大的页。说这个大页前&#xff0c;得先把计算机中内存的管理简单说明一下&#xff0c;否则可能对于一些新手或者把操作系统中内存管理的方法的开发者不太友好。最早的…

langgraph astream使用详解

langgraph中graph的astream&#xff08;stream&#xff09;方法分别实现异步&#xff08;同步&#xff09;流式应答&#xff0c;在langgraph-api服务也是核心方法&#xff0c;实现与前端的对接&#xff0c;必须要把这个方法弄明白。该方法中最重要的参数是stream_mode&#xff…

【C++】模板进阶:非类型参数、模板特化与分离编译

目录 1. 非类型模板参数 2. 模板的特化 3. 分离编译 1. 非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。 非类型形参&#xff0c;就是用一个常量作为类(函数)模板…

栈-1047.删除字符串中的所有相邻重复项-力扣(LeetCode)

一、题目解析 1、反复执行重复项删除操作 2、s仅由小写英文字母组成 二、算法原理 该题并不难&#xff0c;难的是能不能想到用栈这个数据结构解题 解法&#xff1a;栈模拟 横着看起来不好理解&#xff0c;我们把它竖起来&#xff0c;是不是和消消乐很类似&#xff0c;两两消…

【每日算法】移除元素 LeetCode

双指针方法是解决数组或链表问题中非常高效的技巧之一&#xff0c;尤其适用于原地修改数组或减少时间复杂度的场景。以下是对双指针方法的详细讲解。1. 双指针方法的核心思想双指针方法通常使用两个指针&#xff08;或索引&#xff09;在数组或链表中协同工作&#xff0c;通过一…

Android 项目:画图白板APP开发(六)——分页展示

本篇将介绍如何为我们的画板应用添加分页展示功能&#xff0c;让用户可以创建多个画布并在它们之间轻松切换。这章没有啥知识点的讲解&#xff0c;主要介绍一下每页保存的数据结构是什么样的。 一、ListView 多页数据的管理我们使用ListView。之前有文章讲过ListView这里就不多…

智能眼镜产品成熟度分析框架与评估

引言 当前(2025年9月12日),智能眼镜(Smart Glasses)市场正处于快速演进阶段,从早期的新奇设备向主流消费电子转型。AI整合、AR显示和多模态交互的进步推动了这一转变。根据最新数据,2025年AI眼镜发货量预计达686万台,同比增长265%,全球市场规模从2024年的约19.3亿美元…

(网络编程)网络编程套接字 UDP的socket API 代码解析

网络编程基础 为什么需要网络编程?--丰富的网络资源 用户在浏览器中,打开在线视频网站,如优酷看视频,实质是通过网络,获取到网络上的一个视频资源。与本地打开视频文件类似,只是视频文件这个资源的来源是网络。 相比本地资源来说,网络提供了更为丰富的网络资源:所谓的网络资源…

【STM32】状态机(State Machine)

这篇博客介绍 状态机&#xff08;State Machine&#xff09;&#xff0c;适合用于嵌入式开发、驱动开发、协议解析、按键识别等多种场景。 一、什么是状态机&#xff08;State Machine&#xff09;&#xff1f; 状态机&#xff08;State Machine&#xff09;是一种用于描述系统…

深度学习在离岗检测中的应用

离岗检测技术正逐步成为现代企业精细化管理和安全生产的重要工具。这项基于计算机视觉和人工智能的应用&#xff0c;通过自动化、实时化的监测方式&#xff0c;有效提升了工作纪律性和运营效率&#xff0c;为项目管理者和企业提供了创新的监管解决方案。在许多工作场景中&#…

Spring缓存(二):解决缓存雪崩、击穿、穿透问题

1. 缓存穿透问题与解决方案 1.1 什么是缓存穿透 缓存穿透是指查询一个不存在的数据&#xff0c;由于缓存中没有这个数据&#xff0c;每次请求都会直接打到数据库。 如果有恶意用户不断请求不存在的数据&#xff0c;就会给数据库带来巨大压力。 这种情况下&#xff0c;缓存失去了…