01背包问题

news2024/11/19 16:32:13

01背包问题

  • 一、01背包问题
    • 1、问题描述
    • 2、问题性质分析
    • 3、动态规划思路
      • (1)状态方程
        • a.状态表示:
        • b.状态转移:
      • (2)循环的设计
    • 3、代码模板
      • (1)朴素版
      • (2)优化版

一、01背包问题

1、问题描述

在这里插入图片描述

2、问题性质分析

在这里插入图片描述

这道题给我们的第一感觉就是两个字:复杂
为什么我们会觉得复杂呢?其实就是因为我们可做出的选法组合太多了,一下子根本不知道哪个选法是最优的。 说白了,我们感觉复杂的原因就在于问题的规模很大,需要做出的决策很多。复杂的决策问题会让我们无从下手,那么我们可以先从小规模的问题出发,寻找本质。

为了降低问题的复杂程度,我们可以选择缩小规模。缩小规模的方式就是将问题中涉及的变量变得很小。比如,在这道题目中,我们假设就一个物品1,体积是4,价值是5。你的容量是10。

那么此时,选择就很简单了对吧,你就两种选择,要么选物品1,要么不选物品1。很明显,我们选了这件物品就是最优解。

接着,我们稍微拓展一下规模:我们有两个物品:物品1,体积4,价值5,物品2,体积4,价值6。

那么我们此时就面临着两次选择:选不选第二个,选不选第一个。

假设我们选择了第二个物品。那么我们就又到了选择第一个物品的问题上,但此时我们是否选择第一个物品的场景就不同了。
我们此时的剩余容量是5,而上次面对物品1时,我们的容量是10。

那么当我们有三个物品的时候,我们做完物品2,物品3的选择后,是不是还会面对物品1的选择。此时,我们的剩余容量又不同了。

这个时候,我们会发现这个问题中的一些性质,比如:这道问题具有重叠子问题的性质。随着我们的选择,我们总是能遇到相应状态的子问题。

其次,我们不难发现,这道题还具备动态规划的另外两个性质:最优子结构无后效性。为什么呢?

那我们刚才的问题为例,我们解决完物品2的最优选择后,还需要去解决物品1的最优选择问题。也就是说,我们想求当前问题的最优解,就需要去保证子问题的最优解,因此他具备最优子结构的性质

接着,我们发现,我们只面对物品1时的最优解,并不受物品2的影响。所以它也具备无后效性的性质。

此时,我们就能够判断,这道题需要用动态规划的思路。

3、动态规划思路

那么动态规划求解的思路是什么呢?

动态规划就是需要我们提前记录好各个子问题的解,便于我们后续大规模问题的调用。而如何有条理的一步步算出规模不断扩大的子问题呢?

解决这个问题我们需要解决两个事情:

1、不同子问题之间的转换关系(状态方程的表示

2、如何有顺序地扩大子问题规模(循环的书写

(1)状态方程

a.状态表示:

假设我们的物品选择规模是i个,此时的背包容量是j,那么我们可以将当前的问题状态表示为: f ( i , j ) f(i,j) f(i,j),那么 f ( i , j ) f(i,j) f(i,j)的意思就是:当面前共有i个物品,背包容量为j时,我们所能携带的最大价值。

b.状态转移:

状态转移就是表示出当前问题和子问题之间的关系。

i i i物品的价值记作 w [ i ] w[i] w[i],物品的体积记作 v [ i ] v[i] v[i]

状态转移方程的书写思路就是:活在当下。我们现在的目的不是一步解决这个问题,而是通过当前的决策将问题的规模减少

我们遇到了第i个物品,所以我们当下的事情第i个物品选或者不选。两种情况下的最大值,就是我们的 f ( i , j ) f(i,j) f(i,j)的解。

假设我们不选,那么我们此时的状态就只需要面对i-1个物品,背包容量也没变,即 f ( i − 1 , j ) f(i-1,j) f(i1,j)
如果我们选了,那么我们此时的状态也是只需要面对i-1个物品,但此时的容量和价值发生了改变。此时需要注意的是,只有背包容量允许的条件下,我们才可能选。

通过上述思路,我们可以写出下面的转移方程:

f ( i , j ) = { f ( i − 1 , j ) j < v [ i ] m a x ( f ( i − 1 , j ) , f ( i − 1 , j − v [ i ] ) + w [ i ] ) j ≥ v [ i ] f(i,j)= \begin{cases} f(i-1,j) &j<v[i]\\ max\bigg(f(i-1,j),f(i-1,j-v[i])+w[i]\bigg)&j \geq v[i] \\ \end{cases} f(i,j)= f(i1,j)max(f(i1,j),f(i1,jv[i])+w[i])j<v[i]jv[i]

(2)循环的设计

循环设计是为了我们能够及时计算出后续问题需要的子问题结果,并有序地存储起来。我们刚才的举例中会发现,我们在后续问题中最后的子问题永远是第一个物品的选择问题,但面对第一个物品时的容量不同。因此,我们就可以先枚举出不同容量下面对第一个物品的最优解,然后再枚举面对第1,2个物品时,不同容量下的最优解,以此类推…

当然,循环的设计不唯一。
我们可以选择刚刚的设计方式,某个背包面对各个容量的最优解,也可以是某个容量,面对各个背包时的最优解。这两个区别就是内外循环颠倒了一下。二者都能有条不紊地枚举出子问题的结果。但是,前者可以进行空间优化,后者无法进行空间优化。为什么呢?我们后面再说。

3、代码模板

(1)朴素版

根据刚刚的思路,我们就能够写出朴素版的代码

#include<iostream>
using namespace std;
const int N=1e3+10;
int f[N][N],v[N],w[N];
int n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",v+i,w+i);
    }
    
    for(int i=1;i<=n;i++)//枚举背包规模
    {
        for(int j=0;j<=m;j++)//枚举容量
        {
        	//书写状态方程
            f[i][j]=f[i-1][j];
            if(j>=v[i])
            f[i][j]=max(f[i-1][j-v[i]]+w[i],f[i][j]);
        }
    }
    cout<<f[n][m]<<endl;
	return 0;
}

(2)优化版

朴素版的代码中,我们发现,我们当前问题的子问题都存储在了二维数组的上一行,因为写的都是i-1。所以,再往上的子问题记录就没有用了,只是多占空间而已。因此,我们不需要这么大的地方来存储所有子问题。

我们只需要一个一维数组。只需要来回覆盖即可。但是我们按照怎样的顺序来覆盖呢?

答案是我们逆序覆盖,为什么呢?这样做的目的是,保证每个物品只选择一次。为什么呢?

我们可以举个例子:

物品1 :重量 1 价值 15
物品1 :重量 2 价值 30
物品1 :重量 4 价值 45

我们此时的dp数组为 f [ j ] f[j] f[j],这个里面的j表示的是当前的背包容量。假设我们正序遍历,
i=1的时候(意思是面对第一个物品), f ( 1 ) = f ( 1 − 1 ) + 15 f(1)=f(1-1)+15 f(1)=f(11)+15,所以此时的 f ( 1 ) = 15 f(1)=15 f(1)=15那么此时的 f ( 1 ) f(1) f(1)的意思是,我们选择了第一个物品后的最大价值。

此时,内层循环 j++,变成了j=2,所以 f ( 2 ) = f ( 2 − 1 ) + 15 = 30 f(2)=f(2-1)+15=30 f(2)=f(21)+15=30,此时的意思是:在 f ( 1 ) f(1) f(1)的基础上再选择一次第一个物品后的最大价值,我们发现我们选择了两次第一个物品。

而我们如果逆序的话,则可以避免这个问题。大家可以自行举例。

二维数组之所以不需要是因为我们的数据都保存了下来,不会发生覆盖。我们从二维数组的写法中发现,我们用的都是上一行的正对的数据和上一行左侧的数据。所以我们此时不能先覆盖左侧的。从这个角度来看,我们也能够解释遍历顺序的问题。

所以,综合上面所讲的,我们就能够写下下面的代码。

#include<iostream>
using namespace std;
const int N=1e3+10;
int f[N],v[N],w[N];
int n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",v+i,w+i);
    }
    
    for(int i=1;i<=n;i++)//枚举背包规模
    {
        for(int j=m;j>=0;j--)//枚举容量
        {
        	//书写状态方程
            if(j>=v[i])
            f[j]=max(f[j-v[i]]+w[i],f[j]);
        }
    }
    cout<<f[n][m]<<endl;
	return 0;
}

如果我们的朴素版代码内外层循环颠倒,此时我们是无法使用空间优化的。为什么呢?

我们先来看颠倒后的朴素代码:

#include<iostream>
using namespace std;
const int N=1010;
int v[N],w[N],f[N][N];
int n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        v[i]=a,w[i]=b;
    }
    for(int i=0;i<=m;i++)
    {
        for(int j=1;j<=n;j++)
        {
            f[i][j]=f[i][j-1];
            if(i>=v[j])
            f[i][j]=max(f[i][j],f[i-v[j]][j-1]+w[j]);
        }
    }
    cout<<f[m][n]<<endl;
    return 0;
}

在这里插入图片描述

由于用的不是上一行的数据,而是之前某一行的数据,所以我们不能利用覆盖的方式删除之前的数据。因为可能在后来的某一行会用到最开始的第一行数据,跨度很大。我们必须全部保存下来。

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

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

相关文章

WEB接口测试之Jmeter接口测试自动化(初次接触)

软件测试自动化从不同的测试阶段分类&#xff0c;可从下层到上层依次分为单元测试-->接口测试-->界面自动化测试。 单元测试一般有开发人员自行完成&#xff0c;而界面自动化测试合适的测试条件又很难达到&#xff0c;测试人员在繁杂的手工界面测试之余&#xff0c; 更…

相信制造业品牌的实力:专访鸿雁全屋智能贵州总代理

作者 | 牧之 编辑 | 小沐 出品 | 智哪儿 zhinaer.cn长期以来&#xff0c;我们一直在讲智能家居市场的下沉&#xff0c;从一二线城市到三四线城市&#xff0c;从沿海到内陆。而事实上&#xff0c;印象中「欠发达」的内陆城市&#xff0c;对于智能家居的接受度可能比我们想象得要…

Qt中调用gRPC

RPC是Remote Procedure Call的简称&#xff0c;中文叫远程过程调用。 gRPC是由 google开发的一个高性能、通用的开源RPC框架&#xff0c;主要面向移动应用开发且基于HTTP/2协议标准而设计&#xff0c;同时支持大多数流行的编程语言。 一.编译gRPC 操作系统&#xff1a;window…

百亿级流量的系统架构该怎么设计,今天就来教会你!

V-xin&#xff1a;ruyuan0330 获得600页原创精品文章汇总PDF 目录 一、前情提示二、清晰划分系统边界三、引入消息中间件解耦四、利用消息中间件削峰填谷五、手动流量开关配合数据库运维六、支持多系统同时订阅数据七、系统解耦后的感受八、下集预告 一、前情提示 上一篇文章…

吊炸天的云原生,到底是个啥

云原生技术里有很多技术、概念和术语&#xff0c;不了解的人&#xff0c;往往弄不清楚而一头雾水&#xff0c;这些概念都是啥&#xff0c;之间是什么关系&#xff1f;本文要说的就是这些。本文更多是科普和扫盲&#xff0c;无意面面俱到&#xff0c;也无意深入细节。 本文适合一…

Allegro如何合并同名网络铜皮操作指导

Allegro如何合并同名网络铜皮操作指导 Allegro可以将同名网络的铜皮合并起来,如下图,需要把下面两块铜皮合并成一块铜皮 具体操作如下 选择Shape选择merge shapes

剑指Offer-面试题1:整数除法——你真的会用Math.abs吗?

整数除法 题目要求 输入2个int型整数&#xff0c;它们进行除法计算并返回商&#xff0c;要求不得使用乘号*、除号/及求余符号%。当发生溢出时&#xff0c;返回最大的整数值。假设除数不为0。例如&#xff0c;输入15和2&#xff0c;输出15/2的结果&#xff0c;即7。 有问题的…

使用OpenCV的函数polylines()绘制多条相连的线段和多边形;使用函数fillPoly()绘制带填充效果的多边形

函数polylines()可用来根据点集绘制多条相连的线段&#xff0c;也可用来绘制多边形。 函数polylines()有两种原型&#xff0c;这里只向大家介绍比较常用的那种原型。 函数polylines()的C原型如下&#xff1a; void cv::polylines(InputOutputArray img,const Point *const *…

Power BI 11个必学官方示例数据案例(附下载链接)

在开始学习Power BI时&#xff0c;最大的问题就是不知道哪里找数据&#xff0c;或者有数据却对搭建看板毫无头绪&#xff0c; 不知道该从哪里下手。 本文收集整理了官网上最值得学习的11个案例&#xff0c;包括不同行业和分析方法&#xff0c;方便大家按需学习。点击标题即可转…

安徽省建设工程监理人员从业水平能力证书

安徽省建设监理协会会员单位从业人员是指已通过安徽省建设监理协会组织的从业水平能力认定考试&#xff0c;取得《安徽省建设工程监理人员从业水平能力证书》&#xff0c;并在工程建设中从事监理工作的监理工程师和监理员&#xff08;以下简称“监理工程师、监理员”&#xff0…

LLVM中矩阵Matrix的实现分析

1 背景说明 Clang提供了C/C语言对矩阵的扩展支持&#xff0c;以方便用户使用可变大小的二维数据类型来实现计算&#xff0c;目前该特性还是实验版&#xff0c;设计和实现都在变化中。LLVM目前设计为支持小型列矩阵&#xff08;column major&#xff09;&#xff0c;其对矩阵的…

Java字节码介绍

Java字节码 概述 学习 Java 的都知道&#xff0c;我们所编写的 .java 代码文件通过编译将会生成 .class 文件&#xff0c;最初的方式就是通过 JDK 的 javac 指令来编译&#xff0c;再通过 java 命令执行 main 方法所在的类&#xff0c;从而执行我们的 Java 程序。而在这中间所…

【矩阵论】6. 矩阵理论——算子范数

6.2 算子范数 6.2.1 定义 CnC^nCn 上任一向量范数 ∥X∥V\Vert X\Vert_V∥X∥V​ 都产生一个矩阵范数 ∥A∥max⁡x≠0{∥AX∥V∥X∥V}\Vert A\Vert\max_{x\neq 0}\limits \{\frac{\Vert AX\Vert_V}{\Vert X\Vert_V}\}∥A∥x0max​{∥X∥V​∥AX∥V​​} ,X∈CnX\in C^nX∈Cn…

Linux 管理联网 测试网络连通性 -- Ping 命令详解 tracepath命令详解

测试网络的连通性 # 我们测试网络的连通性&#xff0c;一般就是使用的 PIng 命令 Ping &#xff1a; 一般格式 &#xff1a; ping [ 选项 ] < 目标主机名 或 IP 地址 > 常用选项 &#xff1a; - c 数字 &#xff1a; 用于 设定本命令发出的 ICMP 消息包的…

限量,Alibaba首发“Java成长笔记”,差距不止一点点

前言 本文是为了帮大家快速回顾了Java中知识点&#xff0c;这套面试手册涵盖了诸多Java技术栈的面试题和答案&#xff0c;相信可以帮助大家在最短的时间内用作面试复习&#xff0c;能达到事半功倍效果。 本来想将文件上传到github上&#xff0c;但由于文件太大有的都无法显示…

CentOS7使用yum安装Golang(超详细)

使用yum安装Golang前言一、go语言介绍二、yum安装golang1.安装go版本为1.19.41.1执行yum install go&#xff08;报错&#xff09;1.2配置go的安装源1.3执行yum install golang1.4查看go的安装版本2.安装go版本为 1.11rc2&#xff08;这个参考&#xff0c;不用操作&#xff09;…

Docker镜像的原理

centos7系统 包括2部分&#xff0c; linux内核&#xff0c;作用是提供操作系统的基本功能&#xff0c;和机器硬件交互&#xff0c;如何读取磁盘数据&#xff0c;管理网络&#xff0c;使用C编写的&#xff0c;由linus的开发团队&#xff0c;内核只提供操作系统的基本功能和特性…

修改嵌入式 ARM Linux 内核映像中的文件系统

zImage 是编译内核后在 arch/arm/boot 目录下生成的一个已经压缩过的内核映像。通常我们不会使用编译生成的原始内核映像 vmlinux&#xff0c;因其体积很大。因此&#xff0c;zImage 是我们最常见的内核二进制&#xff0c;可以直接嵌入到固件&#xff0c;也可以直接使用 qemu 进…

C++的OpenCV中cv::minAreaRect的返回角度的数值范围是多少?

版本不一样的时候&#xff0c;返回也不一样。 我使用opencv/4.5.5。 下图是使用minAreaRect判定的角度&#xff0c;可以看到&#xff0c;数值范围是[0,90]&#xff0c;看起来很离谱。 画出这张图使用的程序如下&#xff1a; C int main() {std::string prefix1 "/mn…

SpringMvc+Thymeleaf实现数据渲染

Thymeleaf是spring boot推荐使用的模板语法&#xff0c;它可以完全替代 JSP 。 从代码层次上讲&#xff1a;Thymeleaf是一个java类库&#xff0c;它是一个xml/xhtml/html5的模板引擎&#xff0c;可以作为mvc的web应用的view层。 Thymeleaf 提供spring标准方言和一个与 SpringMV…