每周一算法:背包问题(三)多重背包

多重背包

N N N件物品和一个容量是 M M M的背包。第 i i i种物品最多有 s i s_i si件,每件的体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式

第一行两个整数, N N N M M M,用空格隔开,分别表示物品数量和背包容积。

接下来有 N N N 行,每行三个整数 v i v_i vi, w i w_i wi, s i s_i si,用空格隔开,分别表示第 i i i 件物品的体积,价值和数量。

输出格式

输出一个整数,表示最大价值。

样例 #1

样例输入 #1

4 5
1 2 3
2 4 1
3 4 3
4 5 2

样例输出 #1

10

提示

0 < N ≤ 1000 , 0 < M ≤ 2000 0<N≤1000,0<M\le2000 0<N1000,0<M2000

0 < v i , w i , s i ≤ 2000 0<v_i,w_i,s_i≤2000 0<vi,wi,si2000

算法思想

状态表示

多重背包的特点是第 i i i种物品最多有 s i s_i si件。仍可以采用01背包的思想,将处理每种物品作为一个阶段,考虑在不同背包容量情况下的最大价值,将其状态定义为 f [ i ] [ j ] f[i][j] f[i][j],表示对于 i i i种物品,在背包容量为 j j j的情况下,背包获得的最大价值。

状态计算

在当前阶段,对于第 i i i种物品来说,有多种情况可以选择:

  • 放入 0 0 0件,此时的最大价值为前 i − 1 i-1 i1种物品,在背包容量为 j j j的情况下的最大价值 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]
  • 放入 1 1 1件,此时背包的最大价值为前 i − 1 i-1 i1种物品,在背包容量为 j − v i j-v_i jvi的情况下的最大价值 f [ i − 1 ] [ j − v i ] + w i f[i-1][j-v_i]+w_i f[i1][jvi]+wi
  • 放入 2 2 2件,此时背包的最大价值为前 i − 1 i-1 i1种物品,在背包容量为 j − 2 × v i j-2\times v_i j2×vi的情况下的最大价值 f [ i − 1 ] [ j − 2 × v i ] + 2 × w i f[i-1][j-2\times v_i]+2\times w_i f[i1][j2×vi]+2×wi
  • 放入 k k k件,此时背包的最大价值为前 i − 1 i-1 i1种物品,在背包容量为 j − k × v i j-k\times v_i jk×vi的情况下的最大价值 f [ i − 1 ] [ j − k × v i ] + k × w i f[i-1][j-k\times v_i]+k\times w_i f[i1][jk×vi]+k×wi
  • 放入 s i s_i si件,此时背包的最大价值为前 i − 1 i-1 i1种物品,在背包容量为 j − s i × v i j-s_i\times v_i jsi×vi的情况下的最大价值 f [ i − 1 ] [ j − s i × v i ] + k × w i f[i-1][j-s_i\times v_i]+k\times w_i f[i1][jsi×vi]+k×wi

以上情况的前提是背包能够装得下 k k k件第 i i i种物品,也就是背包容量 j ≥ k × v i j\ge k\times v_i jk×vi。那么, f [ i ] [ j ] f[i][j] f[i][j]应该选择所有情况的最大值,即 f [ i ] [ j ] = max ⁡ { f [ i − 1 ] [ j − k × v i ] + k × w i } f[i][j] = \max\{f[i-1][j-k\times v_i]+k\times w_i\} f[i][j]=max{f[i1][jk×vi]+k×wi},其中 0 ≤ k ≤ s i 0\le k\le s_i 0ksi,并且 k × v i ≤ j k\times v_i \le j k×vij

初始状态

f [ 0 ] [ 0 ] f[0][0] f[0][0]表示将前 0 0 0种物品装入容量为 0 0 0的背包中的产生的最大价值为 0 0 0

时间复杂度

  • 状态数 n × m n\times m n×m
  • 状态计算时需要枚举第 i i i件物品的数量 s i s_i si,时间复杂度为 O ( s i ) O(s_i) O(si)

总的时间复杂的为 O ( n × m × s ) O(n\times m\times s) O(n×m×s)

代码实现

#include <iostream>
using namespace std;
const int N = 1010, M = 2010;
int f[N][N];
int main(){int n, m;cin >> n >> m;for(int i = 1; i <= n; i++){int v, w, s;cin >> v >> w >> s;for(int j = 0; j <= m; j++){for(int k = 0; k <= s && k * v <= j; k++){f[i][j] = max(f[i][j], f[i - 1][j - k * v] + k * w);}}}cout<<f[n][m]<<endl;return 0;
}

算法优化

根据上述状态转移方程,考虑能否像完全背包一样的思路进行优化呢?

f [ i ] [ j ] = max ⁡ { f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v ] + w , f [ i − 1 ] [ j − 2 × v ] + 2 × w + . . . + f [ i − 1 ] [ j − s × v ] + s × w } f[i][j] = \max\{f[i-1][j], f[i-1][j-v]+w, f[i-1][j-2\times v]+2\times w+...+f[i-1][j-s\times v]+s\times w\} f[i][j]=max{f[i1][j],f[i1][jv]+w,f[i1][j2×v]+2×w+...+f[i1][js×v]+s×w}

可得, f [ i ] [ j − v ] = max ⁡ { f [ i − 1 ] [ j − v ] , f [ i − 1 ] [ j − 2 × v ] + w , f [ i − 1 ] [ j − 3 × v ] + 2 × w + . . . + f [ i − 1 ] [ j − ( s + 1 ) × v ] + ( s + 1 ) × w } f[i][j - v] = \max\{f[i-1][j - v], f[i-1][j-2\times v]+w, f[i-1][j-3\times v]+2\times w+...+f[i-1][j-(s+1)\times v]+(s+1)\times w\} f[i][jv]=max{f[i1][jv],f[i1][j2×v]+w,f[i1][j3×v]+2×w+...+f[i1][j(s+1)×v]+(s+1)×w}

f [ i ] [ j − v ] f[i][j - v] f[i][jv] f [ i ] [ j ] f[i][j] f[i][j]和对比可以发现,多了一项 f [ i − 1 ] [ j − ( s + 1 ) × v ] + ( s + 1 ) × w f[i-1][j-(s+1)\times v]+(s+1)\times w f[i1][j(s+1)×v]+(s+1)×w。如果计算出 f [ i ] [ j − v ] f[i][j - v] f[i][jv],那么是否能得到 f [ i ] [ j ] f[i][j] f[i][j]呢?举个栗子:

在这里插入图片描述

也就是说,知道前 s + 1 s+1 s+1项的最大值并不能计算出前 s s s项的最大值,因此不能采用完全背包的思想来优化多重背包。

二进制枚举

在计算状态的过程中,需要枚举第 i i i种物品的数量 [ 0 , s i ] [0,s_i] [0,si],这里采用一种更高效的枚举方式——二进制枚举。例如当 s i = 1023 s_i=1023 si=1023时,可以将第 i i i种物品“打包”为:

  • 0 0 0件一组
  • 1 1 1件一组
  • 2 2 2件一组
  • 4 4 4件一组
  • 512 512 512件一组

通过上述组与组之间的组合,可以表示出 [ 0 , 1023 ] [0,1023] [0,1023]之间的任意一个数。如果把每组物品看成是01背包中的一种物品(仅能选择一次),那么就相当于用 10 10 10个新物品来表示原来的第 i i i个物品,通过组合这 10 10 10个新物品就可以枚举出第 i i i个物品的全部方案。

时间复杂度

  • 状态数 n × m n\times m n×m
  • 通过上述思想,原来要枚举 s s s次,现在只需要枚举 l o g s logs logs

总的时间复杂的为 ( n × m × l o g s ) (n\times m\times logs) (n×m×logs)

代码实现

#include <iostream>
using namespace std;
const int N = 1010 * 12, M = 2010;
int v[N], w[N];
int f[M];
int main()
{int n, m, k = 0;cin >> n >> m;for(int i = 1; i <= n; i ++){int a, b, s;cin >> a >> b >> s;//二进制拆分for(int j = 1; j <= s; j *= 2){v[++ k] = j * a;w[k] = j * b;s -= j;}//拆分后还有剩余if(s) v[++ k] = s * a, w[k] = s * b;}n = k; //拆分后实际的物品数量//01背包for(int i = 1; i <= n; i ++)for(int j = m; j >= v[i]; j --)f[j] = max(f[j], f[j - v[i]] + w[i]);cout << f[m];return 0;
}

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

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

相关文章

最强Node js 后端框架学习看这一篇文章就够

距离上次认真花时间写作&#xff0c;似乎已经过了许久许久&#xff0c;前端讲了一个新框架 &#xff0c;叫 Nest.js 下方是课件&#xff0c;有过一定开发经验可跟随视频学习 B站 地址 &#xff1a; https://www.bilibili.com/video/BV1Lg4y197u1/?vd_sourcead427ffaf8a5c8344…

Linux --- 进程控制

目录 1. 进程创建 1.1. 内核数据结构的处理 1.2. 代码的处理 1.3. 数据的处理&#xff1a; 方案一&#xff1a;fork创建子进程的时候&#xff0c;直接对数据进行拷贝处理&#xff0c;让父子进程各自私有一份 方案二&#xff1a;写实拷贝(copy on write) 1.4. fork常规用…

后端Long型数据传到前端js后精度丢失的问题

假设一个场景&#xff0c;MybatisPlus的雪花算法生成long类型主键ID&#xff0c;存入数据库&#xff0c;前端获取到数据后&#xff0c;要执行一个更新操作&#xff08;updateById&#xff09;&#xff0c;但这时会出现无法成功更新的情况&#xff01;这是因为前端在长度大于17位…

24.Python 网络编程之socket编程

目录 1.认识TCP/IP2.socket编程2.1 使用socket2.2 使用socketserver 1.认识TCP/IP 计算机网络就是把各个计算机连接在一起&#xff0c;在网络中的计算机可以互相通信。 网络编程是如何在程序中实现两台计算机的通信。 网络通信是两台计算机上的两个进程之间的通信。 为了把…

【编码魔法师系列_构建型1.3 】抽象工厂模式(Abstract Factory)

学会设计模式&#xff0c;你就可以像拥有魔法一样&#xff0c;在开发过程中解决一些复杂的问题。设计模式是由经验丰富的开发者们&#xff08;GoF&#xff09;凝聚出来的最佳实践&#xff0c;可以提高代码的可读性、可维护性和可重用性&#xff0c;从而让我们的开发效率更高。通…

Linux常用命令——atq命令

在线Linux命令查询工具 atq 列出当前用户的at任务列表 补充说明 atq命令显示系统中待执行的任务列表&#xff0c;也就是列出当前用户的at任务列表。 语法 atq(选项)选项 -V&#xff1a;显示版本号&#xff1b; -q&#xff1a;查询指定队列的任务。实例 at now 10 minu…

一个完整的手工构建的cuda动态链接库工程 03记

1&#xff0c; 源代码 仅仅是加入了模板函数和对应的 .cuh文件&#xff0c;当前的目录结构如下&#xff1a; icmm/gpu/add.cu #include <stdio.h> #include <cuda_runtime.h>#include "inc/add.cuh"// different name in this level for different type…

pygame时序模块time

文章目录 简介时钟对象平抛运动 pygame系列&#xff1a;初步&#x1f48e;加载图像&#x1f48e;图像变换&#x1f48e;直线绘制 简介 之前在更新图形的时候&#xff0c;为了调控死循环的响应时间&#xff0c;用到了time.sleep。而实际上&#xff0c;我们并不需要额外导入其他…

Failed to connect to gitee.com port 443: Time out 连接超时提示【Bug已完美解决-鸿蒙开发】

文章目录 项目场景:问题描述原因分析:解决方案:解决方案1解决方案2:解决方案3:此Bug解决方案总结解决方案总结**心得体会:解决连接超时问题的三种方案**项目场景: 导入Sample时遇到导入失败的情况,并提示“Failed to connect to gitee.com port 443: Time out”连接超…

YouTube Premium 会员白嫖教程

前言 YouTube是美国Alphabet旗下的视频分享网站&#xff0c;也是目前全球最大的视频搜索和分享平台&#xff0c;同时允许用户上传、观看、分享及评论视频 1、点击自己的头像&#xff0c;点击购买内容与会员 2、点击免费试订 3、这里选择个人 4、点击开始试用一个月 5、添加一…

Git 配置文件(.gitignore)

前言 在使用 Git 分布式版本控制系统的时候&#xff0c;有些文件如&#xff1a;数据库的一些配置文件&#xff0c;我们不想让这类文件在远程仓库让 Git 来管理&#xff0c;不想让别人看到&#xff0c;此时就可以自己在 Git 仓库目录下创建 / 在远程仓库创建的时候就配置好 .git…

uniapp中进行地图定位

目录 一、创建map 二、data中声明变量 三、获取当前位置信息&#xff0c;进行定位 四、在methods中写移动图标获取地名地址的方法 五、最终展示效果 一、创建map <!-- 地图展示 --><view class"mymap"><!-- <view class"mymap__map"…

LangChain(0.0.340)官方文档五:Model

LangChain官网、LangChain官方文档 、langchain Github、langchain API文档、llm-universe 文章目录 一、Chat models1.1 Chat models简介1.2 Chat models的调用方式1.2.1 环境配置1.2.2 使用LCEL方式调用Chat models1.2.3 使用内置Chain调用Chat models 1.3 缓存1.3.1 内存缓存…

如何调用 API | 学习笔记

开发者学堂课程【阿里云 API 网关使用教程:如何调用 API】学习笔记&#xff0c;与课程紧密联系&#xff0c;让用户快速学习知识。 课程地址&#xff1a;阿里云登录 - 欢迎登录阿里云&#xff0c;安全稳定的云计算服务平台 如何调用 API 调用 API 的三要素 要调用 API 需要三…

组网技术-交换机

交换机&#xff1a; 分类&#xff1a; 根据交换方式划分&#xff1a; 1.存储转发交换&#xff1a;交换机对输入的数据包先进行缓存、验证、碎片过滤&#xff0c;然后进行转发。 时延大&#xff0c;但是可以提供差错校验&#xff0c;并支持不同速度的输入、输出端口间的交换…

Python读取二进制文件:深入解析与技术实现

目录 一、引言 二、二进制文件的基础 1、二进制文件的组成 2、二进制文件的编码 三、Python读取二进制文件的方法 1、使用内置函数open() 2、使用numpy库 四、处理读取的二进制数据 1、解析数据 2. 转换数据类型 五、总结与展望 1、高效读取二进制文件 2、处理复杂…

ssm医药进出口交易系统源码和论文

ssm医药进出口交易系统源码和论文726 首先,论文一开始便是清楚的论述了系统的研究内容。其次,剖析系统需求分析,弄明白“做什么”,分析包括业务分析和业务流程的分析以及用例分析,更进一步明确系统的需求。然后在明白了系统的需求基础上需要进一步地设计系统,主要包罗软件架构…

电源自动切换初识

【前提&#xff1a;这里以一般的单片机产品为例&#xff0c;使用3.3V的供电系统&#xff0c;常见的USB供电、外接电源设配器供电和电池供电】 一、经典二极管切换电路 这是最简单的电源切换电路&#xff1a;二极管并联&#xff0c;理论上支持无数个电源切换&#xff0c;缺点是…

C++基础 -36- 模板之模板函数

模板函数格式 template <class T> void allexchange(T a,T b) {T c;c*a;*a*b;*bc; }模板函数可以增强函数的通用性 举例说明&#xff0c;使用一个模板函数实现了两个的函数的功能 #include "iostream"using namespace std;void myexchangeint(int* a,int* …

linux作业管理_jobs

4.2 作业管理 是指控制当前正在运行的进程的行为&#xff0c;也称为进程控制。 是shell的一个特性&#xff0c;使用户能在多个独立进程间进行切换。 例如&#xff0c;用户可以挂起一个正在运行的进程&#xff0c;稍后再恢复其运行。当用户使用vim编辑一个文本文件&#xff0c…