DP之背包基础

news2024/12/28 4:03:02

目录

DP简介

01背包问题

采药(01背包例题)

完全背包

疯狂的采药(完全背包例题)

背包变式

装箱问题

砝码称重

质数拆分 

优化思考 


DP简介

全称Dynamic Programming即动态规划 

DP算法是解决多阶段决策过程最优化问题的一种常用方法。

多阶段决策过程是指这样一类特殊的活动过程,过程可以按时间顺序分解成若干个相互联系的阶段,在每一个阶段都需要做出决策,全部过程的决策是一个决策序列。

动态规划算法是解决多阶段决策过程最优化问题的一种常用方法,难度比较大,技巧性也很强。利用动态规划算法,可以优雅而高效地解决很多贪婪算法或分治算法不能解决的问题。

有时遇到暴搜宽搜TLE、贪心分治不理想的情况时,用动态规划常常能够起到出人意料的效果

01背包问题

01背包问题最经典的问题就是给一个背包,让你从中选取n个物品且每个物品只能选一次,求取在背包容量满足大于等于背包内物品总体积的情况下,所能装的物品的最大价值或者最小价值,在对于这种问题时,我们常常利用数学集合的思想来解决

我们将背包当中的物品体积开一个v[N]数组,价值开一个w[N]数组,最后开一个dp二维数组

v[N]一维数组来存储每个物品的体积,w[N]一维数组来存储每个物品的价值

dp[i][j]表示从i个物品当中选取,且体积不超过j的选法,因当第一种情况成立时,返回值为0,第二种情况成立时,选取物品放入,返回的是所选取物品的价值,所以dp二维数组的返回值是此种选法情况的背包中物品的最大价值

对应每个dp[i][j]可以分为两种选择情况:

第一种选择情况为从i - 1个物品当中选取,且总体积不超过j的情况

第二种选择情况为从i个物品当中选取,且总体积不超过j的情况

在对于dp二维数组的模拟过程之中我们发现第二种情况要用i来描述,第一种情况要用i - 1描述,二者不完全一致,因此为了优化,我们将第二种情况进行合理变式为从i - 1个物品当中选取,且总体积不超过j - v[i]的情况

因此dp二维数组在依次枚举的时候就可以这样做变动

f[i][j] = f[i - 1][j];//第一种情况
if(j >= v[i])f[i][j] = max(f[i - 1][j],f[i - 1][j - v[i] + w[i]);//第二种情况

两种情况求取最大值,第二种情况的成立条件是第i件物品的体积小于背包体积

采药(01背包例题)

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”如果你是辰辰,你能完成这个任务吗?

题目描述

第一行有 2 个整数 T和M,用一个空格隔开,T 代表总共能够用来采药的时间,M 代表山洞里的草药的数目
接下来的 M行每行包括两个在1到 100之间(包括 1 和 100)的整数分别表示草药的时间和这株草药的价值。

输入

输出在规定的时间内可以采到的草药的最大总价值。

输出

输出在规定的时间内可以采到的草药的最大总价值

样例输入

70 3
71 100
69 1
1 2

样例输出

3

源代码

这道题目就是很经典的01背包问题,换汤不换药而已。背包中的物品换做了草药,背包的体积换作了最多能够使用的时间,物品的体积换做了采取草药的时间

#include <iostream>
using namespace std;
const int N = 3000+10;
int v[N];//存每株草药采取所花时间 
int w[N];//存每株草药的价值 
int T,M;
int dp[N][N];//存不同情况的不同最大价值 
int main()
{
	cin >> T >> M;
	for(int i = 1;i <= M;i ++ )cin >> v[i] >> w[i];
	for(int i = 1;i <= M;i ++ )
	{
		for(int j = 0;j <= T;j ++ )
		{
			dp[i][j] = dp[i - 1][j];
			if(j >= v[i])dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - v[i]] + w[i]);
		}
	}
	cout << dp[M][T];
	return 0;
}

优化后源代码 

#include <iostream>
using namespace std;
const int N = 3000+10;
int v[N];//存每株草药采取所花时间 
int w[N];//存每株草药的价值 
int T,M;
int dp[N];//存不同情况的不同最大价值 
int main()
{
	cin >> T >> M;
	for(int i = 1;i <= M;i ++ )cin >> v[i] >> w[i];
	for(int i = 1;i <= M;i ++ )
	{
		for(int j = T;j >= v[i];j -- )
		{
			dp[j] = max(dp[j],dp[j - v[i]] + w[i]);
		}
	}
	cout << dp[T];
	return 0;
}

完全背包

简单来说,就是01背包的变形,其本质与01背包相似,只是在完全背包当中,每一个物品可以被选择无数次,而不是只能够选择一次,一般来说,完全背包问题是要进行性优化的,否则会TLE 

疯狂的采药(完全背包例题)

题目描述

LiYuxiang 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”


如果你是 LiYuxiang,你能完成这个任务吗?

此题和原题(采药)的不同点:

1. 每种草药可以无限制地疯狂采摘。

2. 药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

输入

输入第一行有两个整数,分别代表总共能够用来采药的时间 t 和代表山洞里的草药的数目 m。

第2到第 (m+1) 行,每行两个整数,第 (i+1) 行的整数 ai, bi分别表示采摘第 i 种草药的时间和该草药的价值。

输出

输出一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值

样例输入

70 3
71 100
69 1
1 2

样例输出

140

源代码(已优化)

#include <iostream>
using namespace std;
const int N = 1000000+10;
int v[N];//存每株草药采取所花时间 
int w[N];//存每株草药的价值 
int T,M;
int dp[N];//存不同情况的不同最大价值 
int main()
{
	cin >> T >> M;
	for(int i = 1;i <= M;i ++ )cin >> v[i] >> w[i];
	for(int i = 1;i <= M;i ++ )
	{
		for(int j = v[i];j <= T;j ++ )
		{
			dp[j] = max(dp[j],dp[j - v[i]] + w[i]);
		}
	}
	cout << dp[T];
	return 0;
}

背包变式

装箱问题

题目描述

有一个箱子容量为V(正整数,0≤V≤20000),同时有n个物品(0<n≤30,每个物品有一个体积(正整数)。要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。

输入

1个整数,表示箱子容量
1个整数,表示有m个物品
接下来n行,分别表示这n个物品的各自体积

输出

1个整数,表示箱子剩余空间。

样例输入

24
6
8
3
12
7
9
7

样例输出

0

源代码

典型的完全背包问题,只不过求取最大值换做了求取最小值,简单变式即可 

#include <iostream>
using namespace std;
const int N = 1000000+10;
int v[N];//存物品的体积 
int V,M;
int dp[N];//存不同情况的不同最大占用空间
int main()
{
	cin >> V >> M;
	for(int i = 1;i <= M;i ++ )cin >> v[i];
	for(int i = 1;i <= M;i ++ )
	{
		for(int j = v[i];j <= V;j ++ )
		{
			dp[j] = max(dp[j],dp[j - v[i]] + v[i]);
		}
	}
	cout << V - dp[V];
	return 0;
}

砝码称重

题目描述

你有一架天平和 N 个砝码,这 N 个砝码重量依次是 W1, W2, · · · , WN。
请你计算一共可以称出多少种不同的重量?
注意砝码可以放在天平两边。

输入

输入的第一行包含一个整数 N。
第二行包含 N 个整数:W1, W2, W3, · · · , WN。
对于 50% 的评测用例,1 ≤ N ≤ 15。
对于所有评测用例,1 ≤ N ≤ 100,N 个砝码总重不超过 100000。

输出

输出一个整数代表答案。

样例输入

3
1 4 6

样例输出

10

源代码

[样例说明]
能称出的 10 种重量是:1、2、3、4、5、6、7、9、10、11。
1 = 1;
2 = 6 - 4(天平一边放 6,另一边放 4);
3 = 4 - 1;
4 = 4;
5 = 6 - 1;
6 = 6;
7 = 1 + 6;
9 = 4 + 6 - 1;
10 = 4 + 6;
11 = 1 +  4 + 6

此处的dp为状态01数组 

#include <iostream>
using namespace std;
const int N = 3000+10;
int f[N][N];
int w[N];
int n,sum = 0,ans = 0;
//sum为累加砝码的最大值,即最大边界值
//无论砝码如何放置,每个砝码只能使用一次且最小值一定大于等于1 
int main()
{
	cin >> n;
	for(int i = 1;i <= n;i ++ )//输入n个砝码的值 
	{
		cin >> w[i];
		sum += w[i];//累加最大边界值 
	}
	for(int i = 1;i <= n;i ++ )//外层循环砝码 
	{
		f[i][w[i]] = 1;//首先将该砝码重量标记为一种情况 
		for(int j = 1;j <= sum;j ++ )//内层循环砝码值 
		{
			if(f[i - 1][j] == 1)//若上层为1 
			{
				f[i][j] = 1;//则继承此种成立情况
				//对于新加的砝码值进行左右标记 
				f[i][j + w[i]] = 1; 
				f[i][abs(j - w[i])] = 1;
			}
		}
	}
	for(int i = 1;i <= sum;i ++ )//扫描1~最大边界值的砝码值 
	{
		if(f[n][i] == 1)ans ++ ;//若为1则证明此砝码值可以整出来 
	}
	cout << ans;
	return 0;
}

质数拆分 

题目描述

2019可以被分解成若干个两两不同的素数,请问不同的分解方案有多少种?
注意:分解方案不考虑顺序,如 2 + 2017 = 2019 和 2017 + 2 = 2019 属于同一种方案。

输入

输出

55965365465060

样例输入

样例输出

55965365465060

源代码(已优化)

本题拉取素数优化的方法可以了解一下埃氏筛法与线性筛法

#include <iostream>
using namespace std;
const int N = 3000+10;
int prime[N];
bool isprime[N];
int idx = 0;
long long dp[N];
void init()//线性筛法拉取素数表 
{
	for(int i = 2;i <= 2019;i ++ )
	{
		if(!isprime[i])
		{
			prime[ ++ idx ] = i;
		}
		for(int j = 1;j <= idx && i <= 2019 / prime[j];j ++ )
		{
			isprime[i * prime[j]] = 1;
			if(i % prime[j] == 0)break;
		}
	}
}
int main()
{
	init();
	dp[0] = 1;//注意 
	for(int i = 1;i <= idx;i ++ )
	{
		for(int j = 2019;j >= prime[i];j -- )
		{
			dp[j] = dp[j] + dp[j - prime[i]];
		}
	}
	cout << dp[2019];
	return 0;
}

优化思考 

二维数组的开辟范围当然是限制性很大的,所以利用dp二维数组并不能够用来表示当物品种类过大的情况,那么动态规划的算法也就有了鸡肋性,不过幸好,我们可以将一维数组优化为二维数组,优化的方式是将代码等价变形,并减少非必要的元素遍历

当我们将dp数组的首坐标删去之后,我们可以发现循环里面的

dp[i][j] = dp[i - 1][j];

变成了

dp[j] = dp[j];

因此首行代码可以省去

根据第二行代码的情况进行减少数据遍历的优化,当第二行的情况成立时,j应该大于等于v[i],也就是说在从0~v[i] - 1的情况都是无意义的遍历,因此的原本两种情况可以合并为一种情况

if(j >= v[i])dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - v[i]] + w[i]);

换做

dp[j] = max(dp[j],dp[j - v[i]] + w[i]);

但是此时变形之后的一维数组相当于

dp[i][j] = max(dp[i - 1][j],dp[i][j - v[i]] + w[i]);

然而我们想要的是

dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - v[i]] + w[i]);

所以我们要对于第二重循环的j进行改变 

for(int i = 1;i <= M;i ++ )
{
	for(int j = 0;j <= T;j ++ )
	{
		dp[i][j] = dp[i - 1][j];
		if(j >= v[i])dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - v[i]] + w[i]);
	}
}

调整之后为

for(int i = 1;i <= M;i ++ )
{
	for(int j = T;j >= v[i];j -- )
	{
		dp[j] = max(dp[j],dp[j - v[i]] + w[i]);
	}
}

谨记

第二重循环从大到小遍历之后的优化为01背包优化也就是

dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - v[i]] + w[i]);

第二重循环从小到大的遍历为完全背包优化也就是

dp[i][j] = max(dp[i - 1][j],dp[i][j - v[i]] + w[i]);

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

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

相关文章

04软件测试需求分析案例-用户登录

通读文档&#xff0c;提取信息&#xff0c;提出问题&#xff0c;整理为需求。 从需求规格说明、设计说明、配置说明等文档获取原始需求&#xff0c;通读原始需求&#xff0c;分析有哪些功能&#xff0c;每种功能要完成什么业务&#xff0c;业务该如何实现&#xff0c;业务逻辑…

【MySQL】踩坑笔记——保存带有换行符等特殊字符的数据,需要进行转义保存

问题描述 从DBeaver中导出了部分业务数据的 insert sql&#xff0c;明明在开发、测试环境都可以一把执行通过&#xff0c;却在预发环境执行前的语法检查失败了&#xff0c;提示有SQL语法错误。 这条SQL长这样&#xff0c;default_sql是要在odps上执行的sql语句&#xff0c;提…

windos挂载目录到linux

验证环境麒麟V10 1: 在windows任意目录设置共享文件夹 2&#xff1a;记住网络路径\LAPTOP-86JV6NT1\gantie13_sdk 在linux中替换为本机ip级相对路径 比如本级ip是192.168.23.23&#xff0c;linux环境需要ping通本地地址 3&#xff1a; sudo apt-get install cifs-utils sud…

springboot494基于java的综合小区管理系统(论文+源码)_kaic

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统综合小区管理系统信息管理难度大&#xff0c;容错率低&am…

数据分析篇001

目录 一、数据是什么&#xff1f; 二、数据能做什么&#xff1f; 三、数据应用四步骤 第一步---搭建数据体系 第二步---积累数据资产 第三步---完成数据分析 第四步---实现数据应用 四、数据的三种性质 变异性 规律性&#xff08;以正态分布为例&#xff09; 客观性…

ModbusTCP从站转Profinet主站案例

一. 案例背景 在复杂的工业自动化场景中&#xff0c;企业常常会采用不同品牌的设备来构建生产系统。西门子SINAMICS G120变频器以其高性能、高精度的速度和转矩控制功能&#xff0c;在电机驱动领域应用广泛。施耐德M580可编程逻辑控制器则以强大的逻辑控制和数据处理能力著称&…

flask后端开发(8):Flask连接MySQL数据库+ORM增删改查

目录 数据库初始化数据库连接创建数据库表添加用户查询用户更新用户删除 在Flask中&#xff0c;很少会使用pymysql直接写原生SQL语句去操作数据库&#xff0c;更多的是通过SQLAichemy提供的ORM技术&#xff0c;类似于操作普通Python对象一样实现数据库的增删改查操作&#xff0…

【社区投稿】自动特征auto trait的扩散规则

自动特征auto trait的扩散规则 公式化地概括&#xff0c;auto trait marker trait derived trait。其中&#xff0c;等号右侧的marker与derived是在Rustonomicon书中的引入的概念&#xff0c;鲜见于Rust References。所以&#xff0c;若略感生僻&#xff0c;不奇怪。 marker …

【Go】context标准库

文章目录 1. 概述1.1 什么是 Context1.2 设计原理1.3 使用场景1.4 Context 分类核心:Context接口2. 源码解读4个实现emptyCtxTODO 和 BackgroundcancelCtxWithCancelcancelCtx.propagateCancel 构建父子关联parentCancelCtx 获取父上下文中的内嵌cancelCtxcanceltimerCtxWithT…

【视觉惯性SLAM:四、相机成像模型】

相机成像模型介绍 相机成像模型是计算机视觉和图像处理中的核心内容&#xff0c;它描述了真实三维世界如何通过相机映射到二维图像平面。相机成像模型通常包括针孔相机的基本成像原理、数学模型&#xff0c;以及在实际应用中如何处理相机的各种畸变现象。 一、针孔相机成像原…

CAPL_构建基于UDS的刷写学习—04 思路的构建_第一部分

前言与导读&#xff1a; 基于前几篇文章我们已经梳理了HEX文件、S19文件的读取和UDS关键的0x34/0x36/0x37等服务的结构。 基于此&#xff0c;我们差不多就完成了前期的知识储备了&#xff0c;那么完成最终的目的——使用capl实现我们还需要解决以下几个问题。 1、文件如何读…

分布式 IO 模块助力冲压机械臂产线实现智能控制

在当今制造业蓬勃发展的浪潮中&#xff0c;冲压机械臂产线的智能化控制已然成为提升生产效率、保障产品质量以及增强企业竞争力的关键所在。而分布式 IO 模块的应用&#xff0c;正如同为这条产线注入了一股强大的智能动力&#xff0c;开启了全新的高效生产篇章。 传统挑战 冲压…

香橙派5Plus启动报错bug: spinlock bad magic on cpu#6, systemd-udevd/443

一、问题 如图&#xff1a; 接上调试串口&#xff0c;每次启动都会报错。不过使用过程中没有发现有什么影响。 百度查阅&#xff0c;有一位博主提到&#xff0c;但是没有细说解决方案&#xff1a; spinlock变量没有初始化_spinlock bad magic on-CSDN博客https://blog.csdn.n…

Matrix-Breakout 2 Morpheus(找到第一个flag)

第一步 信息收集 (1)寻找靶场真实ip arp-scan -l 靶场真实 ip 为192.168.152.154 (2)探测端口及服务 nmap -p- -sV 192.168.52.135 第二步 开始渗透 (1)访问web服务 http://192.168.152.154and http://192.168.52.135:81 发现 81 端口的页面要登录 我们使用 dirb 扫描…

学习C++:关键字

关键字&#xff1a; 作用&#xff1a;关键字是C预先保留的单词&#xff08;标识符&#xff09; 在定义变量或者常量时候&#xff0c;不要用关键字 不要用关键字给变量或者常量起名称

Android笔记(四十):ViewPager2嵌套RecyclerView滑动冲突进一步解决

背景 ViewPager2内嵌套横向滑动的RecyclerView&#xff0c;会有滑动冲突的情况&#xff0c;引入官方提供的NestedScrollableHost类可以解决冲突问题&#xff0c;但是有一些瑕疵&#xff0c;滑动横向RecyclerView到顶部&#xff0c;按住它不放手继续往左拖再往右拖&#xff0c;这…

【提审】Android包提审报权限问题

问题&#xff1a;华为应用市场审核不通过 平台审核检测详情&#xff1a; 日志&#xff1a; 自检工具&#xff1a;frida-server【Unity&Android】安卓app自测应用隐私相关获取和申请权限_apk 隐私合规 自测-CSDN博客 参考资料&#xff1a;Unity启动时获取了android_id等设…

QtQuick之QML应用程序开:一、使用资源文件以及给应用程序添加图标

开发环境: 1、Qt Creator 14.0.1 2、windows10 先看下面的步骤,不明白再返回来看下面官方指导链接。 先看下面的步骤,不明白再返回来看下面官方指导链接。 先看下面的步骤,不明白再返回来看下面官方指导链接。 --------------------------------------------------------…

Task :prepareKotlinBuildScriptModel UP-TO-DATE,编译卡在这里不动或报错

这里写自定义目录标题 原因方案其他思路 原因 一般来说&#xff0c;当编译到这个task之后&#xff0c;后续是要进行一些资源的下载的&#xff0c;如果你卡在这边不动的话&#xff0c;很有可能就是你的IDE目前没有办法进行下载。 方案 开关一下IDE内部的代理&#xff0c;或者…

webauthn介绍及应用

1、webauthn介绍 官网&#xff1a;https://webauthn.io/ 1.1、什么是webauthn&#xff1f; webauthn即Web Authentication&#xff0c;是一个符合W3C标准的Web认证规范。它通过公私钥加密技术&#xff0c;实现无密码认证&#xff0c;用户仅需通过pin码、指纹、面部识别、usb …