01背包、完全背包、多重背包、分组背包总结

news2024/11/19 10:26:55

文章目录

    • 一、01背包问题
    • 二、完全背包问题
    • 三、多重背包问题
    • 四、分组背包

一、01背包问题

在这里插入图片描述

n个物品,每个物品的重量是 w i w_i wi,价值是 v i v_i vi,背包的容量是 m m m

若每个物品最多只能装一个,且不能超过背包容量,则背包的最大价值是多少?

模板

int n;              // 物品总数
int m;              // 背包容量
int v[n];           // 价值 
int w[n];           // 重量

二维形式

// f[i][j]表示在考虑前i个物品后,背包容量为j条件下的最大价值
int f[n][m];
// 先遍历物品,后遍历容量
for(int i = 1; i <= n; ++i){
	for(int j = 1; j <= m; ++j){
		if(j < w[i]) f[i][j] = f[i-1][j];   //  当前重量装不进,价值等于前i-1个物品   
        else f[i][j] = max(f[i-1][j], f[i-1][j-w[i]] + v[i]); // 能装,需判断  
	}
}
cout << f[n][m];

一维形式

int f[m];   // f[j]表示背包容量为j条件下的最大价值
for(int i = 1; i <= n; ++i) 
    for(int j = m; j >= w[i]; --j)
        f[j] = max(f[j], f[j - w[i]] + v[i]);           // 注意是倒序,否则出现写后读错误
cout << f[m];           // 注意是m不是n

注意f[i][j]的含义:在考虑前i个物品后,背包容量为j条件下的最大价值。而不是表示选了i个物品的最大价值,实际上选择的物品数<=if[j]表示背包容量为j条件下的最大价值

二维压缩成一维,实际上是寻找避开写后读错误的方法:

f[i][j]始终只用上一行的数据f[i-1][...]更新(迭代更新的基础,如果还需用上上行数据则不可压缩)
f[i][j]始终用靠左边的数据f[i-1][<=j]更新(决定了只能倒序更新)

显然i=0时,f(i,j)=0,而初始化时自动赋予0,故不必但单独处理第0行

二、完全背包问题

在这里插入图片描述
假设背包容量为j时,最多可装入k个物品ik不能无限大,因为背包容量有限,则有:

f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i − 1 , j − w i ) + v i , f ( i − 1 , j − 2 w i ) + 2 v i , ⋯ , f ( i − 1 , j − k w i ) + k v i ) f(i,j)=max(f(i−1,j),f(i−1,j−w_i)+v_i,f(i−1,j−2w_i)+2v_i,⋯,f(i−1,j−kw_i)+kv_i) f(i,j)=max(f(i1,j),f(i1,jwi)+vi,f(i1,j2wi)+2vi,,f(i1,jkwi)+kvi)

上述max括号里总共k+1项


考虑到:

f ( i , j − w i ) = m a x ( f ( i − 1 , j − w i ) , f ( i − 1 , j − 2 w i ) + v i , f ( i − 1 , j − 3 w i ) + 2 v i , ⋯ , f ( i − 1 , j − k w i ) + ( k − 1 ) v i ) f(i,j−w_i)=max(f(i−1,j−w_i),f(i−1,j−2w_i)+v_i,f(i−1,j−3w_i)+2v_i,⋯,f(i−1,j−kw_i)+(k−1)v_i) f(i,jwi)=max(f(i1,jwi),f(i1,j2wi)+vi,f(i1,j3wi)+2vi,,f(i1,jkwi)+(k1)vi)

上述max括号里总共k项


上式变形得:

f ( i , j − w i ) + v i = m a x ( f ( i − 1 , j − w i ) , f ( i − 1 , j − 2 w i ) + v i , f ( i − 1 , j − 3 w i ) + 2 v i , ⋯ , f ( i − 1 , j − k w i ) + ( k − 1 ) v i ) + v i f(i,j−w_i)+v_i=max(f(i−1,j−w_i),f(i−1,j−2w_i)+v_i,f(i−1,j−3w_i)+2v_i,⋯,f(i−1,j−kw_i)+(k−1)v_i)+v_i f(i,jwi)+vi=max(f(i1,jwi),f(i1,j2wi)+vi,f(i1,j3wi)+2vi,,f(i1,jkwi)+(k1)vi)+vi

因此我们得到:

f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i , j − w i ) + v i ) f(i,j)=max(f(i−1,j),f(i,j-w_i)+v_i) f(i,j)=max(f(i1,j),f(i,jwi)+vi)

也就是f[i][j]可以由其左侧的f[i,j-w[i]]和其正上方的f[i−1][j]推导出来

在这里插入图片描述
未优化的二维形式

// f[i][j]表示在考虑前i个物品后,背包容量为j条件下的最大价值
int f[N][M];    
for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++)
    	// 第三重循环遍历上述的椭圆,找最大值,分别表示物品i取 0 ... j/w[i]次
        for (int k = 0; k <= j / w[i]; k++)
            f[i][j] = max(f[i][j], f[i - 1][j - k * w[i]] + k * v[i]);   // 注意和01背包的对比
cout << f[n][m];

// 最内层的for循环求出 f[i-1][j] , f[i-1][j-w[i]] + v[i],  f[i-1][j-2*w[i]] + 2*v[i], ..., f[i-1][j-k*w[i]] + k*v[i]中的最大值

优化的二维形式

// f[i][j]表示在考虑前i个物品后,背包容量为j条件下的最大价值
int f[N][M];    
for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++)
    	if(j < v[i]) f[i][j] = f[i-1][j];   //  当前重量装不进,价值等于前i-1个物品   
        else f[i][j] = max(f[i-1][j], f[i][j - w[i]] + v[i]); // max(不装物品i,装物品i(包含了装1个...装j/w[i]个的情况))  
cout << f[n][m];

// f[i][j - w[i]] + v[i] = max(装0个物品i,装1个物品i,...,装k个物品i) + v[i] 
// f[i][j - w[i]] + v[i] = max(f[i-1][j-w[i]], f[i-1][j-2*w[i]]+v[i], ..., f[i-1][j-(k+1)*w[i]]+k*v[i]) + v[i]
// f[i][j - w[i]] + v[i] = max(f[i-1][j-w[i]] + v[i], f[i-1][j-2*w[i]]+2*v[i], ..., f[i-1][j-(k+1)*w[i]]+(k+1)*v[i])
// f[i][j - w[i]] + v[i] = max(f[i-1][j-w[i]] + v[i], f[i-1][j-2*w[i]]+2*v[i], ..., f[i-1][j-(k+1)*w[i]]+(k+1)*v[i])
// f[i][j - w[i]] + v[i] = max(装1个,...,装j/w[i]个)  

优化的一维形式

// f[i][j]表示在考虑前i个物品后,背包容量为j条件下的最大价值
for (int i = 1; i <= n; i++)
    for (int j = w[i]; j <= m; j++)
        f[j] = max(f[j], f[j - w[i]] + v[i]); // max(不装物品i,装物品i(包含了装1个...装j/w[i]个的情况))  
cout << f[m];

注意: 内层循环遍历容量时,一定要是顺序的,因为我们在优化后的二维形式中写道,二维数组中计算f[i][j]时,就是需要使用到左侧(同一层)的计算结果,对于一维形式来说就是要使用已经计算后的结果。一维形式中,如果逆序遍历容量,计算后面容量时,使用的就是没有计算过的结果,体现在二维中,使用的就是上一层的结果,这是不对的

01背包和完全背包问题的一维写法只差了一个遍历的顺序,01背包使用逆序遍历,完全背包使用顺序遍历,因为转换到二维形式,01背包只需要使用的是上一层的结果,完全背包需要使用当前层的结果

三、多重背包问题

01背包:物品i最多选1次
完全背包:物品i可以选无数次
多重背包:物品i最多选 s i s_i si

由于多重背包和完全背包都是可以物品选多次,所以状态转移方程也一样,只是多了一个限制条件

朴素写法如下:

f[i][j] = max(f[i-1][j], f[i-1][j-k*w[i]]+k*v[i]) // k为 0 -> s[i]
// f[i][j]表示在考虑前i个物品后,背包容量为j条件下的最大价值
int f[N][M];    
for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++)
    	// 第三重循环遍历上述的椭圆,找最大值,分别表示物品i取几次
        for (int k = 0; k <= j / w[i] && k <= s[i]; k++)
            f[i][j] = max(f[i][j], f[i - 1][j - k * w[i]] + k * v[i]);   // 注意和01背包的对比
cout << f[n][m];

我们试试使用完全背包问题的优化方式

f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i − 1 , j − w i ) + v i , f ( i − 1 , j − 2 w i ) + 2 v i , ⋯ , f ( i − 1 , j − s i w i ) + s i v i ) ( 1 ) f(i,j)=max(f(i−1,j),f(i−1,j−w_i)+v_i,f(i−1,j−2w_i)+2v_i,⋯,f(i−1,j−s_iw_i)+s_iv_i)\quad\quad\quad(1) f(i,j)=max(f(i1,j),f(i1,jwi)+vi,f(i1,j2wi)+2vi,,f(i1,jsiwi)+sivi)(1)

上述max括号里总共s[i]+1项


考虑到:

f ( i , j − w i ) = m a x ( f ( i − 1 , j − w i ) , f ( i − 1 , j − 2 w i ) + v i , f ( i − 1 , j − 3 w i ) + 2 v i , ⋯ , f ( i − 1 , j − s i w i ) + ( s i − 1 ) v i ) , f ( i − 1 , j − ( s i + 1 ) w i ) + s i v i ) ( 2 ) f(i,j−w_i)=max(f(i−1,j−w_i),f(i−1,j−2w_i)+v_i,f(i−1,j−3w_i)+2v_i,⋯,f(i−1,j−s_iw_i)+(s_i−1)v_i),\\\quad\quad\quad\quad\quad\quad\quad f(i−1,j−(s_i+1)w_i)+s_iv_i)\quad\quad\quad\quad\quad\quad\quad(2) f(i,jwi)=max(f(i1,jwi),f(i1,j2wi)+vi,f(i1,j3wi)+2vi,,f(i1,jsiwi)+(si1)vi),f(i1,j(si+1)wi)+sivi)(2)

max(放0个物品i,放1个物品i,放2个物品i,放s[i]个物品i)

上述max括号里总共s[i]+1项


上式变形得:

f ( i , j − w i ) + v i = m a x ( f ( i − 1 , j − w i ) + v i , f ( i − 1 , j − 2 w i ) + 2 v i , f ( i − 1 , j − 3 w i ) + 3 v i , ⋯ , f ( i − 1 , j − s i w i ) + s i v i , f ( i − 1 , j − ( s i + 1 ) w i ) + ( s i + 1 ) v i ) ) ( 3 ) f(i,j−w_i)+v_i=max(f(i−1,j−w_i)+v_i,f(i−1,j−2w_i)+2v_i,f(i−1,j−3w_i)+3v_i,⋯,f(i−1,j−s_iw_i)+s_iv_i,\\\quad\quad\quad\quad\quad\quad\quad\quad\quad f(i−1,j−(s_i+1)w_i)+(s_i+1)v_i))\quad\quad\quad(3) f(i,jwi)+vi=max(f(i1,jwi)+vi,f(i1,j2wi)+2vi,f(i1,j3wi)+3vi,,f(i1,jsiwi)+sivi,f(i1,j(si+1)wi)+(si+1)vi))(3)

我们现在需要根据(3)式的结果,推出(1)式的结果,(1)式的后s[i]项和(3)式的前s[i]项完全一样,但我们无法使用(3)式中s[i]+1项的最大值,推出(3)式的前s[i]项的最大值,所以无法使用完全背包问题的优化方式

二进制优化

根据二进制表示,可以知道 1 , 2 , 4 , ⋯ , 2 k 1,2,4,⋯,2^k 1,2,4,,2k 可以由系数0和1线性组合出 [ 0 , 2 k − 1 ] [0,2^k-1] [0,2k1],如果我们用k+1个新的物品,来代替多重背包的一个物品,使用01背包的方式计算出这k+1个物品选或不选

比如说S=200,我们使用1、2、4、8、16、32、64、73这8个物品,代替200这个物品,对这8个物品进行选或不选,我们就能表示出当前这个物品选[0,200]次

按照上面的,如果给我们一个一般的S,我们使用1、2、4、8、…、 2 k 2^k 2k、C,组合出S,则我们有 1 + 2 + 4 + 8 + . . . + 2 k ≤ s 1+2+4+8+...+2^k\leq s 1+2+4+8+...+2ks,以及 0 ≤ C < 2 k + 1 \\0\leq C<2^{k+1} 0C<2k+1

也就是说,给我们一个物品最多选择S次,其实就是有S个这样的物品,我们可以将S个相同的物品,按照二进制的方式合并成 l o g 2 S + 1 log_2S+1 log2S+1个物品,再对这 l o g 2 S + 1 log_2S+1 log2S+1个物品使用01背包算法,如果说原来有n类物品,每类物品有s个,总共 n s ns ns个,我们合并后,就变成了总共 n ( l o g 2 S + 1 ) n(log_2S+1) n(log2S+1)

// 读入物品个数时顺便打包
int k = 1;              // 当前包裹大小
int idx = 0;
// n表示原来物品数量,循环读入n个物品的重量,价值,数量
for(int i = 1; i <= n; i++){
	cin >> wi >> vi >> si;   // 物品重量,物品价值,物品数量
	// 开始用si个物品合成log2 si个物品
	while (k <= si){
	    idx++ ;              // 实际物品种数
	    w[idx] = wi * k;
	    v[idx] = vi * k;
	    si -= k;
	    k *= 2;             // 二进制倍增包裹大小
	}
	if(si > 0){
		idx++;
		w[idx] = wi * si;
		v[idx] = vi * si;
	}
}
//现在的n表示合成后的物品数量
int n = idx;
for(int i = 1; i <= n; i++){
	for(int j = m; j >= v[i]; j--){
		f[j] = max(f[j], f[j-w[i]]+v[i]);
	}
}
cout << f[m];

四、分组背包

完全背包问题是考虑第i个物品选几个,分组背包问题是考虑第i组物品选哪个或不选(每组物品中至多拿1个)

实际上是带有约束的01背包问题,状态计算为: f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i − 1 , j − w ( i , k ) ) + v ( i , k ) ) f(i,j)=max(f(i−1,j),f(i−1,j−w(i,k))+v(i,k)) f(i,j)=max(f(i1,j),f(i1,jw(i,k))+v(i,k))

在这里插入图片描述

// n组物品
for(int i = 1; i <= n; i++){
	cin >> s[i];
	for(int j = 1; j <= s[i]; j++){
		cin >> w[i][j] >> v[i][j];
	}
}

// 遍历物品组数
for(int i = 1; i <= n; i++){
	// 遍历背包容量
	for (int j = m; j >= 1; j -- ){
		// 组内遍历物品
		for(int k = 0; k <= s[i]; k++){
			f[j] = max(f[j], f[j-w[i][k]]+v[i][k]);
		}
	}
}
cout << f[m] << endl;

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

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

相关文章

【ABAP】SAP发送消息至RabbitMQ

SAP发送消息至RabbitMQ ——以下关于RabbitMQ的内容大致转载于朱忠华老师的《RabbitMQ实战指南》一书 【基础知识】 消息队列中间件(Message Queue Middleware,即MQ)也可以称之为消息队列或者消息中间件,是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数…

面试官: B 树和 B+ 树有什么区别?

问各位小可爱一个问题&#xff1a;MySQL 中 B 树和 B 树的区别&#xff1f; 请自己先思考5秒钟&#xff0c;看看是否已经了然如胸&#xff1f; 好啦&#xff0c;时间到&#xff01; B 树和 B 树是两种数据结构&#xff0c;构建了磁盘中的高速索引结构&#xff0c;因此不仅 …

上海亚商投顾:沪指窄幅震荡 “中字头”概念股又暴涨

上海亚商投顾前言&#xff1a;无惧大盘大跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪沪指今日窄幅震荡&#xff0c;深成指、创业板指盘中跌超1%&#xff0c;午后探底回升一度翻红。光伏、储能等赛道午后…

[Spring Cloud] GateWay自定义过滤器/结合Nacos服务注册中心

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

DocuWare Workflow Manager(工作流管理器)

DocuWare Workflow Manager 公司是按流程运转的。销售、人力资源、财务等部门需要流畅、可靠的信息传输&#xff0c;以便在正确的时间做出正确的决策。订单管理、员工入职和发票审批等流程可以根据您的精确需求进行设计和自动化&#xff0c;避免时间浪费。 适用于复杂业务的简…

Mysql数据库相关面试题

1.关系型和非关系型数据库的区别是什么? 关系型和非关系型数据库的主要差异是数据存储的方式,关系型数据库天然就是表格存储,因此存储在数据表的行和列中,数据表可以彼此关联协作存储,很容易提取数据. 优点: 易于维护:都是使用表结构,格式一致,使用方便:sql语言通用,可以用于复…

MyBatis逆向工程和分页插件

1、分页插件 MyBatis 通过提供插件机制&#xff0c;让我们可以根据自己的需要去增强MyBatis 的功能。需要注意的是&#xff0c;如果没有完全理解MyBatis 的运行原理和插件的工作方式&#xff0c;最好不要使用插件&#xff0c; 因为它会改变系底层的工作逻辑&#xff0c;给系统带…

2022年全国职业院校技能大赛:网络系统管理项目-模块B--Windows样题7

初始化环境1.默认账号及默认密码 Username: Administrator Password: ChinaSkill22! Username: demo Password: ChinaSkill22! 注:若非特别指定,所有账号的密码均为 ChinaSkill22! 项目任务描述你作为技术工程师,被指派去构建一个公司的内部网络,要为员工提供便捷、安…

超算云平台在线功能Q-Flow、Q-Studio V2.1版本升级,web端在线建模+DFT计算

建模DFT计算还可以这么玩&#xff1f; Q-Flow&#xff08;在线可视化提交任务功能&#xff09;以及 Q-Studio&#xff08;在线建模功能&#xff09;依托Mcloud平台免费向用户开放使用。告别Linux编辑代码提交任务的模式&#xff0c;Q-Flow可在浏览器里通过拖拽图形化的第一性原…

【第06节】Selenium4 JavaScript 处理场景实战(Python Web自动化测试)

Selenium 4 【01-06节】主讲元素定位&#xff0c;处理一些特殊场景的方法与实战已经全部写完。文章所有素材来自互联网&#xff0c;如果文章有侵权处&#xff0c;请联系作者。 文章目录1、Selenium4 自动化 JavaScript 场景实战1.1 使用 JavaScript 处理富文本1.2 使用 JavaScr…

Linux——网络编程总结性学习

什么是ISP&#xff1f; 网络业务提供商_百度百科 计算机网络有哪些分类方式,计算机网络有哪些分类&#xff1f;_陈泽杜的博客-CSDN博客 路由器_百度百科 目前实际的网络分层是TCP/IP四层协议 当我们浏览⽹站找到想要下载的⽂件以后&#xff0c;找到相应的接⼝点击下载就好了。…

新形势下安全风险评估实践

​ 随着安全内涵的不断扩充和发展&#xff0c;风险评估作为安全管理的重点&#xff0c;内容以及方法都与时俱进的得到了发展和丰富&#xff0c;本文将介绍新形势下风险评估的特点和实践心得&#xff0c;以供参考。 一、新形势下安全风险评估特点 首先是内外部形势和要求的变…

Docker入门教程(详细)

目录 一、Docker概述 1.1 Docker 为什么出现&#xff1f; 1.2 Dorker历史 1.3 能做什么 虚拟机技术&#xff1a;&#xff08;通过 软件 模拟的具有完整 硬件 系统功能的、运行在一个完全 隔离 环境中的完整 计算机系统&#xff09; 容器化技术&#xff1a;&#xff08;容…

【JAVA高级】——玩转JDBC中的三层架构

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java案例分…

Oracle SQL执行计划操作(8)——视图与集合相关操作

8. 视图相关操作 该类操作与包含视图的SQL语句相关。当SQL语句被硬解析时,如果SQL语句中的视图未被合并,则根据不同的具体场景,如下各操作可能会出现于相关SQL语句的执行计划。 1)VIEW 创建数据的一个中间视图,一般分为系统视图和用户视图。优化器在为SQL语句生成执行计…

iOS上架流程详细版本

苹果上架审核周期长一直是困扰用户的一大问题&#xff0c;这次把我自己上架的经历分享给大家&#xff0c;避免大家入坑。 上架总流程&#xff1a; 创建开发者账号 借助辅助工具appuploader创建证书&#xff0c;描述文件 iTunes connect创建App 打包IPA上传App Store等待审…

Kubernetes(k8s)CNI(flannel)网络模型原理

一、概述 Kubernetes 采用的是基于扁平地址空间的、非NAT的网络模型&#xff0c;每个Pod有自己唯一的IP地址。网络是由CNI(container network interface)插件建立的&#xff0c;而非K8S本身。 二、常见的几种CNI插件介绍 为了使容器之间的通信更加方便&#xff0c;Google 和 Co…

计算机网络面试大总结

本文分文五大部分&#xff0c;第一部分总纲说明计算机网络层次划分的三种模型&#xff0c;一到四部分以TCP/IP协议模型作为划分标准&#xff0c;分别说明各层作用和最常见的面试题&#xff0c;最后总结网络综合面试题&#xff0c;历时六天全文一千字。 其他经典面试题参考程序员…

虹科校园大使招募令

虹科校园大使招募令 我们正式邀请你成为虹科校内明星代言人&#xff01; 近距离接触技术大牛工作领域 来自人力总监的职业发展指导 官方校园大使认证证书 走内部通道提前斩获校招offer 你将成为 校园品牌首席推广师 赋予你自主“DIY”的权利&#xff0c;与校招负责人一起…

损失函数——机器学习

目录 一、实验内容 二、实验过程 1、算法思想 2、算法原理 3、算法分析 三、源程序代码 四、运行结果分析 五、实验总结 一、实验内容 理解损失函数的基本概念&#xff1b;理解并掌握均方差损失函数的原理&#xff0c;算法实现及代码测试分析&#xff1b;理解并掌握交叉…