完全背包问题(超级详细地讲解优化过程)

news2024/11/19 12:32:56

完全背包问题

  • 一、问题描述
  • 二、思路分析
    • 1、状态转移方程
    • 2、循环设计
  • 三、代码模板
    • 1、朴素版
    • 2、优化版
      • (1)时间优化
      • (2)空间优化

一、问题描述

在这里插入图片描述

二、思路分析

完全背包和01背包的区别就在于01背包中,每个物品只能选择一次,而完全背包问题中,每个物品可以选择无限次。如果大家没有看过之前01背包的讲解的话,建议大家先去看看作者之前写的01背包问题,传送门:01背包问题

那么很明显,这道题符合动态规划的三个性质:最优子结构,重叠子问题,无后效性。因此,我们可以利用动态规划的思路去解决这道题。这三个性质的分析和01背包是一样的。

那么想要利用动态规划的思路来解决这道题的话,我们需要做两件事情:

1、构建当前问题和子问题之间的关系:书写状态转移方程
2、设计循环,记录每一个子问题的最优解。

1、状态转移方程

状态转移方程的作用是我们通过数学表达式来达到缩小问题规模。所以,我们需要用子问题来解决当前状态。而在上一节01背包问题中,我们曾经介绍过,对于背包问题中状态转移方程的书写,我们的方式是:活在当下

我们面前要做的选择就是第i个物品,你到底是选还是不选,如果选你能选多少?

很明显,只要在容量允许的范围内,我们可以选很多个,最后我们在各种方案内选出一个最大值。

如下所示:

f ( i , j ) = m a x { f ( i − 1 , j ) f ( i − 1 , j − v [ i ] ∗ 1 ) + w [ i ] ∗ 1 . . . f ( i − 1 , j − v [ i ] ∗ k ) + w [ i ] ∗ k k ∗ v [ i ] ≤ j f(i,j)=max \begin{cases} f(i-1,j)\\ f(i-1,j-v[i]*1)+w[i]*1\\ ...\\ f(i-1,j-v[i]*k)+w[i]*k&k*v[i]\leq j \end{cases} f(i,j)=max f(i1,j)f(i1,jv[i]1)+w[i]1...f(i1,jv[i]k)+w[i]kkv[i]j

2、循环设计

循环的设计其实就是为了有条不紊地逐渐计算出规模不断增大地子问题。完全背包的循环设计和01背包的循环设计是一致的。我们逐一枚举1个物品时,各个容量下的最优解,2个物品时,各个容量下的最优解,直到n个物品下,各个容量下的最优解。

三、代码模板

1、朴素版

#include<iostream>
using namespace std;
const int N=1010;
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++)//枚举容量
        {
            for(int k=0;k*v[i]<=j;k++)//书写状态方程
            {
                f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k);
            }
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

2、优化版

(1)时间优化

我们发现上面的朴素版代码的嵌套了三重循环,这个时间复杂度是非常高的,我们如何降低时间复杂度呢?

同时,我们发现,我们的完全背包问题依旧是只利用 i − 1 i-1 i1行的数据。那么我们能否像01背包一样将二维数组转化成一维数组呢?

我们先解决时间复杂度优化的问题:

我们再看一下我们刚才的状态转移方程:
f ( i , j ) = m a x { f ( i − 1 , j ) f ( i − 1 , j − v [ i ] ∗ 1 ) + w [ i ] ∗ 1 . . . f ( i − 1 , j − v [ i ] ∗ k ) + w [ i ] ∗ k k ∗ v [ i ] ≤ j f(i,j)=max \begin{cases} f(i-1,j)\\ f(i-1,j-v[i]*1)+w[i]*1\\ ...\\ f(i-1,j-v[i]*k)+w[i]*k&k*v[i]\leq j \end{cases} f(i,j)=max f(i1,j)f(i1,jv[i]1)+w[i]1...f(i1,jv[i]k)+w[i]kkv[i]j
我们刚刚的最内层循环其实就是在执行上面这个状态转移方程,最内层的循环在枚举每一种情况,然后去得出一个最大值。

那么我们就聚焦于这个过程:

假设我们当前的背包容量是 j − v [ i ] j-v[i] jv[i],那么此时我们会去枚举该容量下的 k k k种情况。最终得到一个最大值,我们将这个最大值记作: m a x ( j − v [ i ] ) max(j-v[i]) max(jv[i])。此时,我们会将这个最大值记录在数组中。

接着,随着我们的第二层循环的进行,我们到了背包容量为 j j j的情况,此时,我们就会开始枚举 k k k种情况,去求出最大值。但是此时我们思考一下,我们是否还有必要去枚举所有的情况?

k k k = 1 的时候,

我们需要比较的是 : f ( i , j ) f(i,j) f(i,j) f ( i , j − v [ i ] ) + w [ i ] f(i,j-v[i])+w[i] f(i,jv[i])+w[i]

此时假设我们得到的是下面这种结果:

f ( i , j ) > f ( i , j − v [ i ] ) + w [ i ] f(i,j) > f(i,j-v[i])+w[i] f(i,j)>f(i,jv[i])+w[i]

接下来,

k = 2 k=2 k=2的时候,

此时,我们会得到 f ( i , j − v [ i ] ∗ 2 ) + w [ i ] ∗ 2 f(i,j-v[i]*2)+w[i]*2 f(i,jv[i]2)+w[i]2,而这个式子可以改写为: f ( i , ( j − v [ i ] ) − v [ i ] ) + w [ i ] + w [ i ] f\big(i,(j-v[i])-v[i]\big)+w[i] +w[i] f(i,(jv[i])v[i])+w[i]+w[i]

那么这个式子的前半部分: f ( i , ( j − v [ i ] ) − v [ i ] ) + w [ i ] f\big(i,(j-v[i])-v[i]\big)+w[i] f(i,(jv[i])v[i])+w[i] 就可以看作是,我们刚才求状态 f ( i , j − v [ i ] ) f(i,j-v[i]) f(i,jv[i])的最大值时的枚举 k = 1 k=1 k=1的过程。

而我们在此之前我们已经求得了 m a x ( j − v [ i ] ) max(j-v[i]) max(jv[i])。所以此时一定满足:

f ( i , ( j − v [ i ] ) − v [ i ] ) + w [ i ] ≤ m a x ( j − v [ i ] ) f(i,(j-v[i])-v[i])+w[i]\leq max(j-v[i]) f(i,(jv[i])v[i])+w[i]max(jv[i])

两侧再同时加上一个 w [ i ] w[i] w[i]并不影响我们的结果,即:

f ( i , ( j − v [ i ] ) − v [ i ] ) + 2 ∗ w [ i ] ≤ m a x ( j − v [ i ] ) + w [ i ] f(i,(j-v[i])-v[i])+2*w[i]\leq max(j-v[i])+w[i] f(i,(jv[i])v[i])+2w[i]max(jv[i])+w[i]
我们将这个式子一般化:

我们后续过程枚举的任意一个 k k k,都满足下列不等式:

f ( i , j − k ∗ v [ i ] ) + k ∗ w [ i ] = f ( ( i − v [ i ] ) − ( k − 1 ) ∗ v [ i ] ) + ( k − 1 ) ∗ w [ i ] + w [ i ] f(i,j-k*v[i])+k*w[i]=f\big((i-v[i])-(k-1)*v[i]\big)+(k-1)*w[i]+w[i] f(i,jkv[i])+kw[i]=f((iv[i])(k1)v[i])+(k1)w[i]+w[i]
f ( i , ( j − v [ i ] ) − ( k − 1 ) v [ i ] ) + ( k − 1 ) w [ i ] + w [ i ] ≤ m a x ( j − v [ i ] ) + w [ i ] f\big(i,(j-v[i])-(k-1)v[i]\big)+(k-1)w[i]+w[i]\leq max(j-v[i])+w[i] f(i,(jv[i])(k1)v[i])+(k1)w[i]+w[i]max(jv[i])+w[i]

因此,我们与其去枚举每个k,不如直接用之前求出的 m a x ( j − v [ i ] ) max(j-v[i]) max(jv[i])

于是,我们只需要进行一次下列等式的比较即可:
f ( i , j ) = m a x ( f ( i , j ) , m a x ( j − v [ i ] ) + w [ i ] ) f(i,j)=max\big(f(i,j),max(j-v[i])+w[i]\big) f(i,j)=max(f(i,j),max(jv[i])+w[i])

那么用数组表示就是:
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ j − v [ i ] ] + w [ i ] ) f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]) f[i][j]=max(f[i][j],f[i][jv[i]]+w[i])

上述式子表达的意思是,我们选择第 i i i个物品时的最大值,但是我们是不是还有可能不选。
所以我们需要在选的情况下的最大值,和不选的情况下的值做比较。

所以,经过优化后的状态转移方程如下:
m a x = { f [ i ] [ j ] j < v [ i ] m a x ( f [ i ] [ j ] , f [ i ] [ j − v [ i ] ] + w [ i ] ) j ≥ v [ i ] max= \begin{cases} f[i][j]&j<v[i] \\max\big(f[i][j],f[i][j-v[i]]+w[i]\big)&j\geq v[i] \end{cases} max={f[i][j]max(f[i][j],f[i][jv[i]]+w[i])j<v[i]jv[i]

上述的式子就可以直接代替第三层循环。

时间优化版本:

#include<iostream>
using namespace std;
const int N=1100;
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][j],f[i][j-v[i]]+w[i]);
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

(2)空间优化

时间+空间优化的版本:

我们在01背包的文章中曾经介绍说,逆序进行第二层循环是为了避免重复选择。而我们的完全背包是允许进行重复选择的。如果大家没看过之前的01背包问题的文章的话,作者建议先去看一看01背包的优化,01背包传送门

因此,我们正序遍历第二层循环的情况下就可以进行空间优化,代码如下:

#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=0;j<=m;j++)
        {
            if(j>=v[i])
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

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

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

相关文章

Java架构师大厂面试致命十连问,你接得住吗?

1.什么是缓存雪崩&#xff1f;怎么解决&#xff1f; ​ 编辑切换为居中 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 通常&#xff0c;我们会使用缓存用于缓冲对 DB 的冲击&#xff0c;如果缓存宕机&#xff0c;所有请求将直接打在 DB&#xff0c;造…

故事分享|27岁的Leader:要成为别人的灯塔,自己得先会“发光”

学习编程的年龄跨度很大&#xff0c;有还在读小学的10后小朋友&#xff0c;也有子孙满堂的八十岁老太太&#xff0c;但主力军&#xff0c;当属90后。 很多年前&#xff0c;90后还是许多人口中“垮掉的一代”。 许多年过去了&#xff0c;当90后逐渐摘掉不成熟的标签&#xff0…

ssh前置代理

ssh前置代理ssh前置代理Linux和mac配置ssh前置proxyUbuntu和mac的ncCentos的ncWindows的ssh前置proxyssh前置代理 适用于服务器无法直接连接过去,需要用proxy才可以连接的场景. Linux和mac配置ssh前置proxy nc属命令属于nmap-ncat包 Centos的nmap-ncat版本太低了,需要到https:…

学习笔记 - MapStruct 映射工具

学习笔记 - MapStruct 映射工具简介Maven 依赖实体类 Entity数据传输对象 DTO映射接口测试类IDEA 插件与 Lombok 一起使用参考资料简介 MapStruct是一个代码生成器&#xff0c;它基于约定优于配置的方法&#xff0c;极大地简化了Java bean类型之间映射的实现。 生成的映射代码使…

第8章 关系数据库设计

第8章 关系数据库设计 考试范围&#xff1a; 8.1-8.5&#xff0c;8.8&#xff0c;8.9 考试题型: 模式分解 考试内容&#xff1a; INF概念 非规范化设计的问题&#xff1a;数据冗余&#xff0c;插入/删除/更新异常 函数依赖的概念 平凡函数依赖 函数依赖集 最小(正则)覆…

数据结构和算法学习笔记之 03.单向双向链表和环形链表构建

5.单向链表 把一个节点Node当做是一个对象&#xff0c;改对象里面包含了数据和指向下一个节点的引用指针 5.1 链表的添加和遍历 5.1.1 思路分析 添加 创建一个head头节点表示链表的头节点&#xff0c;里面的存放数据的data null每添加一个元素就直接添加到链表的最后(尾插法…

Practise test day9

另类加法_牛客网 解题思路&#xff1a;位运算符 1 0001 2 0010 按位与&&#xff1a;如果两个二进制位都为1&#xff0c;则返回1&#xff0c;否则返回0 按位异或&#xff1a;两个二进制位相同返回0&#xff0c;不同返回1。 1.二进制位异或的结果&#xff0c;是两个数对应相加…

https-OPenSSL证书生成及自签名证书

目录 SSL/TLS 1、搭建OPenssl服务器 1.1、下载 1.2、安装下载好的exe程序 2、服务器端证书-生成key、CSR、CRT 2.1、进入如下目录&#xff0c;执行cmd 2.2、生成一个私钥key 2.3、由生成的私钥key生成一个待签名的CSR证书文件(公钥) 2.4、查看证书内容 3、自建CA证书 3.1…

跨境电商卖家如何创建客户参与的 Facebook 广告?

关键词&#xff1a;跨境电商卖家、客户参与、Facebook广告 想要从您的 Facebook 广告中获得更多潜在客户或转化&#xff1f;正在寻找为您自己的广告建模的成功秘诀&#xff1f; 在本文中&#xff0c;您将了解创建消费者响应的 Facebook 广告的八个技巧。 将您现有的 Facebook 受…

零基础能否转行做程序员,那些半路出家的程序员大神给你做了榜样

这些年&#xff0c;随着中国互联网产业的高速发展&#xff0c;对程序员这个职业的需求越来越大。而相对较高的薪水、简单的人际关系、入行不需要靠拼爹这些优点&#xff0c;也让越来越多的年轻人选择了这个职业。甚至很多本来已经从事了其他行业的年轻人&#xff0c;也都想转行…

Promise(三) promise自定义封装25-35

1.初始结构搭建 2.resolve和reject结构搭建 3.throw抛出异常改变状态 4.promise对象状态只能修改一次 5.then方法执行回调 6.指定多个回调的实现 7.同步修改状态then方法结果返回 8.异步修改状态then方法结果返回 9.then方法完善与优化 10.catch方法——异常穿透与值管…

网络技术基础复习——计算机网络基本概念七层模型

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 前言 本章将会复习网络技术的一些知识&#xff0c;了解网络基础概念&#x…

如何学习python?python该怎么学?如何高效率学习python?分享python的学习资料和网站

如何学习python&#xff1f; 1、学好python的第一步&#xff0c;就是马上到网站上下载一个python版本。我建议初学者&#xff0c;不要下载具有IDE功能的集成开发环境&#xff0c;比如Eclipse插件等。 2、下载完毕后&#xff0c;就可以开始学习了。学习过程中&#xff0c;我建议…

商业报表工具-FineReport JS 实现动态隐藏参数以及控制参数面板高度

1. 概述 1.1 版本 报表服务器版本 功能变更 11.0 -- 1.2 预期效果 开发报表的时&#xff0c;经常会遇到使用的参数控件较多的情况&#xff0c;这时候可以对一些不常用、不需要展现出来的的控件进行隐藏&#xff0c;当需要展示的时候再使其展示出来&#xff0c;如下图所示&…

数字化信道

数字化信道 数字化信道主要包括多相滤波和DFT两个模块。 多相滤波 多相滤波&#xff0c;就是将滤波器系数按照相数进行重排。在D倍抽取后&#xff0c;整个频带的频谱将混叠在0频附近[−Fs2D,Fs2D)[-\frac{F_s}{2D} ,\frac{F_s}{2D})[−2DFs​​,2DFs​​)。因此&#xff0c;…

超标量处理器设计——第四章_分支预测

超标量处理器设计——第四章_分支预测 参考《超标量处理器》姚永斌著 4.1 简述 分支预测主要与预测两个内容, 一个是分支方向, 还有一个是跳转的目标地址首先需要识别出取出的指令是否是分支指令, 如果是需要送入方向和地址预测模块: 分支预测最好的时机就是当前周期取到指令地…

【C++】拷贝构造函数

目录 默认拷贝构造函数 拷贝构造函数的原型&#xff1a; 为什么不用值传递&#xff1f; 为什么不用指针传递&#xff1f; 调用拷贝构造函数的3种情况 (旧对象去构造新对象) 我们熟悉的类型有以下操作&#xff1a; 1.声明&#xff1a;int a; 2.声明并初始化&#xff1a;in…

圣诞节怎么能缺少圣诞树呢?Python+HTML打造专属于你的圣诞树

前言&#xff1a; 美酒一杯让人醉&#xff0c;温馨陪伴浪漫随;雪花片片惹人爱&#xff0c;烦恼忧伤全不见;字里行间藏真情&#xff0c;文短情深送心愿:圣诞佳节快来到&#xff0c;祝大家永远开心幸福! Hello大家好&#xff0c;我是Dream。 圣诞节马上到了&#xff0c;一些朋友问…

非零基础自学Golang 第14章 反射 14.2 基本用法 14.2.1 获取类型信息

非零基础自学Golang 文章目录非零基础自学Golang第14章 反射14.2 基本用法14.2.1 获取类型信息第14章 反射 14.2 基本用法 在使用反射时&#xff0c;我们会经常使用到反射的种类&#xff08;Kind&#xff09;&#xff0c;reflect.Kind在Go语言reflect包中的定义如下&#xff…

认知水平越低的人,越喜欢回复“收到”

前几天后台有位粉丝留言&#xff1a; “有一次回复’收到’&#xff0c;竟被领导痛批一顿&#xff01; 领导什么活都让我干&#xff0c;找资料、填表格、开会&#xff0c;虽然明知忙不过来&#xff0c;内心也不乐意做个打杂工&#xff0c;但嘴巴却只能缓缓吐出‘收到’两个字……