【No.15】蓝桥杯动态规划上|最少硬币问题|0/1背包问题|小明的背包1|空间优化滚动数组(C++)

DP初步:状态转移与递推

最少硬币问题
  • 有多个不同面值的硬币(任意面值)
  • 数量不限
  • 输入金额S,输出最少硬币组合。
    回顾用贪心求解硬币问题
    硬币面值1、2、5。支付13元,要求硬币数量最少
    贪心:
    (1)5元硬币,2个
    (2)2元硬币,1个
    (3)1元硬币,1个
    硬币面值1、2、4、5、6.,支付9元。
    贪心:
    (1)6元硬币,1个
    (2)2元硬币,1个
    (3)1元硬币,1个
    错误!
    答案是:5元硬币+4元硬币=2个
硬币问题的正解是动态规划

type = [1,5,10,25,50] 5种面值
定义数组Min[],记录最少硬币数量:
对输入的某个金额i,Min[i]是最少的硬币数量
第一步,只考虑1元面值的硬币

		金额i: 0,1,2,3,4,5
硬币数量`Min[]`:0,1,2,3,4
  • i=1元时,等价于:i = i - 1 = 0 元需要的硬币数量,加上1个1元硬币
  • i=2元时,等价于:i = i - 1 = 1 元需要的硬币数量,加上1个1元硬币

  • 在1元硬币的计算结果基础上,再考虑加上5元硬币的情况,从i=5开始就行了
    i=5元时,等价于
  1. i = i - 5 = 0元需要的硬币数量,加上1个5元硬币,Min[5] = 1
  2. 原来的Min[5] = 5
    取1和2的最小值,所以Min[5] = 1
    i = 6元时,等价于
  3. i = i - 5 = 1元需要的硬币数量,加上1个5元硬币,Min[6] = 2
  4. 原来的Min[6] = 6
    取1和2的最小值,所以Min[6] = 2
    Min[6] = Min[5] + 1
    递推关系:
    Min[i] = min (Min[i], Min[i - 5] + 1)
    继续处理其它面值硬币
#include <iostream>
#include <vector>
#include <limits.h>
using namespace std;void solve(int s)
{int cnt = 5;   //5种硬币vector<int> type = {1,5,10,25,50};  //5种面值vector<int> Min(s+1, INT_MAX);   //初始化为无穷大Min[0] = 0;for (int j = 0; j < cnt; j ++)  //5种硬币{Min[i] = min (Min[i], Min[i - type[j]] + 1);}cout << Min[s] << endl;
}int main()
{int s;cin >> s;solve(s);return 0;
}
DP的两个特征
  1. 重叠子问题
    在递归算法中,尤其是在解决最优化问题时,经常会遇到这样的情况:在求解大问题的过程中,我们需要多次求解规模更小、结构相同的问题。这些小问题被称为子问题。如果这些子问题在大问题求解过程中被重复计算多次,这将导致算法效率低下,因为大量时间被重复的子问题求解所占据。动态规划通过存储子问题的解(通常在二维数组中,称为DP表),确保每个子问题只计算一次,从而避免了重复计算。当需要某个子问题的解时,直接从DP表中查找,如果该子问题尚未解决,则先解决它,然后存储其解。
  2. 最优子结构
    这是动态规划能够成功解决许多问题的另一个关键特性。最优子结构是指一个问题的最优解包含其子问题的最优解。换句话说,如果我们能找到所有子问题的最优解,那么我们可以通过这些子问题的最优解来构建原问题的最优解。动态规划利用这个性质,通过自底向上的方式(即先解决最基础的子问题,然后逐步解决更大规模的子问题)来构建问题的最优解。
DP:记忆化

如果各个子问题不是独立的,如果能够保存已经解决的子问题的答案,在需要的时候再找出已求得的答案,可以避免大量的重复计算。
基本思路:用一个表记录所有已解决的子问题的答案,不管该问题以后是否被用到,只要它被计算过,就将其结果填入表中。
记忆化
解题步骤

  • 拆分问题
  • 定义状态(并找出初状态)
  • 状态转移方程
    一般的模型方法
  • 递归搜索法
  • 记忆化搜索(记忆化暴力)
  • 递推式法
最经典的DP问题:0/1背包

给定n种物品和一个背包,物品i的重量是 w i w_{i} wi其价值为 v i v_{i} vi,背包的容量为C.
背包问题:选择装入背包的物品,使得装入背包中物品的总价值最大
如果在选择装入背包的物品时,对每种物品i只有两种选择:装入背包或不装入背包,称为0/1耆包问题,
与装载问题不同的是,0/1背包不能只装一部分,要么选,要么不选。

x i x_{i} xi表示物品i装入背包的情况
x i x_{i} xi=0,表示物品i没有被装入背包
x i x_{i} xi=1,表示物品i被装入背包
约束条件:
∑ i = 1 n w i x i ≤ C x i ∈ { 0 , 1 } ( 1 ≤ i ≤ n ) \begin{array}{} \sum_{i=1}^{n}w_{i}x_{i} \le C \\ x_{i}\in \left \{ 0,1 \right \}(1 \le i \le n) \end{array} i=1nwixiCxi{0,1}(1in)
目标函数:
m a x ∑ i = 1 n v i x i max\sum_{i=1}^{n}v_{i}x_{i} maxi=1nvixi
例:有5个物品,重量分别是{2,2,6,5,4},价值分别为{6,3,5,4,6},背包容量为10
定义一个(n+1)(C+1)的二维表dp[][]
dp[i][j]表示把前i个物品装入背包中花费容量为j的情况下获得的最大价值

012345678910
0
1
2
3
4
5
填表,按只放第一个物品,只放前2个,只放前3个…一直到放完,这样的顺序考虑(从小问题扩展到大问题)
  1. 只装第一个物品(横向是递增的背包容量)
012345678910
000000000000
100666666666
2
3
4
5
  1. 只装前2个物品
    如果第2个物品重量比背包容量大,那么不能装第2个物品,情况和只装第1个一样
    如果第2个物品重量小于背包容量,那么
    1. 如果把物品2装进去(重量是2),那么相当于只把1装到(容量-2)的背包中
    2. 如果不装2,那么相当于只把1装到背包中
    3. 取1和2的最大值
012345678910
000000000000
100666666666
200669999999
3
4
5
  1. 只装前3个物品
    如果第3个物品重量比背包大,那么不能装第3个物品,情况和只装第1、2个一样。
    如果第3个物品重量小于背包容量,那么
    1. 如果把物品3装进去(重量是6),那么相当于只把1、2装到(容量-6)的背包中
    2. 如果不装3,那么相当于只把1、2装到背包中
    3. 取1和2的最大值
012345678910
000000000000
100666666666
200669999999
300669999111114
4
5

按这样的规律一行行填表,直到结束,现在回头考虑,装了那些物品,看最后一列,15>14,说明装了物品5,否则价值不会变化

012345678910
000000000000
100666666666
200669999999
300669999111114
4006699910111314
50066991212151515
小明的背包1

【题目描述】小明有一个容量为C的背包。这天他去商场购物,商场一共有N件物品,第i件物品的体积为 c i c_{i} ci,价值为 w i w_{i} wi。小明想知道在购买的物品总体积不超过C的情况下所能获得的最大价值为多少,请你帮他算算。
【输入描述】输入第1行包含两个正整数 N,C,表示商场物品的数量和小明的背包容量。
第 2~N+1 行包含 2个正整数c,w,表示物品的体积和价值。 1 ≤ N ≤ 1 0 2 1 \le N\le 10^2 1N102, 1 ≤ C ≤ 1 0 3 1 \le C\le 10^3 1C103, 1 ≤ w i , c i ≤ 1 0 3 1 \le w_{i},c_{i}\le 10^3 1wi,ci103
【输出描述】输出一行整数表示小明所能获得的最大价值。

DP状态设计

DP状态:定义二维数组dp[][],大小为NxC
dp[i][j]:把前i个物品(从第1个到第i个)装入容量为j的背包中获得的最大价值。
把每个dp[i][j]看成一个背包:背包容量为j,装1~i这些物品。最后得到的dp[N][C]就是问题的答案:把N个物品装进容量c的背包的最大价值。

DP状态转移方程

递推计算到dp[i][j]分2种情况:

  1. 第i个物品的体积比容量j还大,不能装进容量j的背包。那么直接继承前i-1个物品装进容量j的背包的情况即可:dp[i][j]= dp[i-1][j]
  2. 第i个物品的体积比容量j小,能装进背包。又可以分为2种情况:装或者不装第i个,
    1. 装第i个,从前i-1个物品的情况下推广而来,前i-1个物品是dp[i-1][j]。第i个物品装进背包后,背包容量减少c[i],价值增加w[i]。有:dp[i][j]= dp[i-1][j-c[i]] + w[i]
    2. 不装第i个,那么:dp[i][j] = dp[i-1][j]
    3. 取1和2的最大值
      状态转移方程
      dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - c[i]] + w[i])
代码
#include <bits/stdc++.h>
using namespace std;const int N = 3011;
int w[N], c[N];   //物品的价值和体积
int dp[N][N];
int solve (int n, int C)
{for (int i = 1; i <= n; i++){for (int j = 0; j <= C; j++){if (C[i]>j)  //第i个物品比背包还大,装不了dp[i][j] = dp[i-1][j];else  //第i个物品可以装dp[i][j] = max(dp[i-1][j], dp[i-1][j-c[i]] + w[i]);}}return dp[n][C];
}
int main()
{int n, C;cin >> n >> C;for (int i = 1; i <= n; i++){cin >> c[i] >> w[i];}memset(dp, 0, sizeof(dp));  //清0cout << solve(n, C);return 0;
}
空间优化:滚动数组

dp[][]优化成一维的dp[],以节省空间。
Dp[i][]是从上面一行dp[i-1]算出来的,第i行只跟第i-1行有关系,跟更前面的行没有关系!
dp[i][j]=max(dp[i-1][j],dp[i-1][j- c[i]]+ w[i])
优化:只需要两行dp[0][]dp[1][],用新的一行覆盖原来的一行,交替滚动。
经过优化,空间复杂度从O(NxC)减少为O©。

定义dp[2][j]:用dp[0][]dp[1][]交替滚动。
优点:逻辑清晰、编码不易出错,建议初学者采用这个方法
因为我们新一行的计算只与上一行有关所以,两行重复使用即可
伪代码:

int w[N], c[N];  //物品的价值和体积
int dp[2][N];    //替换int dp[][];
solve (int n, int C)
{now = 0, old = 1;  //now指向当前正在计算的一行,old指向旧的一行for (int i = 1; i <= n; i ++){//交替滚动,now始终指向最新的一行if(c[i] > j)dp[now][j] = dp[old][j];elsedp[now][j] = max(dp[old][j], dp[old][j - c[i]] + w[i]);}return dp[now][C];  //返回最新的行
}
自我滚动

因为状态转移每次只与上一层有关,所以用一个一维数组就可以。
继续精简:用一个一维的dp[]就够了,自己滚动自己。
dp[j]=dp[j-c[i]]+w[i]
为什么从大到小遍历,看dp[j]=dp[j-c[i]]+w[i]这一状态转移,是根据小的改大的,如果先把小的改了那小的还会被用到,数据就不对了,所以从大到小

for (int i = 0; i < n; i ++) //遍历每一件物品
{//遍历背包容量,表示在上一层的基础上,容量为j时,第i件物品装或不装的最优解for (int j = C; j >= c[i]; j --){dp[j] = max(dp[j-c[i]] + w[i], dp[j]);}
}

j从小往大循环是错误的

0123456789
dp[j]'0066666666
dp[j]0066696666

例如i=2时,上图的dp[5]经计算得到dp[5]=9,把dp[5]更新为9。

0123456789
dp[j]'0066696666
dp[j]00666966126

下图中继续往后计算,当计算dp[8]时,得dp[8]=dp[5]'+3=9+3=12这个答案是错的。
错误的产生是滚动数组重复使用同一个空间引起的.

j从大到小循环是对的
例如i = 2时,首先计算最后的dp[9] = 9,它不影响前面状态的计算
1.

0123456789
dp[j]'0066666666
dp[j]0066666669
0123456789
dp[j]'0066666669
dp[j]0066666699
初始化细节

装满 dp[0]=0,其余赋值-INF;不装满全初始化为 0;
若一定要求装满:
则必有n=sum(c[i]) i ∈ i \in i已选集合
所以dp[n-sum(c[i])]= dp[0]
所以只有从dp[0]出发才合法,那就把其他的设成无穷小。

//装满
memset (dp, -0x3f, sizeof(dp));
dp[0] = 0;
//不装满
memset (dp, 0, sizeof(dp));

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

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

相关文章

【微服务】设计弹性微服务架构模式

目录 模式#1 — 超时模式#2 — 重试模式#3— 隔离模式#4— 断路器模式#5 — 冗余推荐超级课程: Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战在微服务架构中,服务通常相互协作以提供业务用例。这些服务可能在可用性、可伸缩性、弹性等方面具有…

LeetCode-热题100:3. 无重复字符的最长子串

题目描述 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 "abc"&#xff0c;所以其长度为 3。 示例 2: 输入: s “bbbbb” 输出: 1 解释: 因为无重复字…

数据分析-Pandas分类数据的比较如何避坑

数据分析-Pandas分类数据的比较如何避坑 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据表…

Lombok简单使用

1、介绍 Lombok是一个Java库&#xff0c;它通过注解的方式简化了Java代码的编写。它提供了一些注解&#xff0c;可以自动生成一些常用的代码&#xff0c;如getter和setter方法、构造函数、equals和hashCode方法等。使用Lombok可以减少冗余的代码&#xff0c;提高开发效率。 2…

Rust 程序设计语言学习——结构体

结构体和元组类似&#xff0c;它们都包含多个相关的值。和元组一样&#xff0c;结构体的每一部分可以是不同类型。但不同于元组&#xff0c;结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字&#xff0c;结构体比元组更灵活&#xff1a;不需要依赖顺序来…

医院预约挂号系统设计与实现|jsp+ Mysql+Java+ Tomcat(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;…

【WPF应用11】如何对StackPanel中的控件进行间距设置?

在WPF中&#xff0c;堆叠面板&#xff08;StackPanel&#xff09;是一个常用的布局控件&#xff0c;它允许您将子控件垂直或水平堆叠起来。在设计用户界面时&#xff0c;合理的间距设置可以提高界面的美观性和易用性。本文将介绍如何在StackPanel控件中设置控件之间的间距&…

初识kafka-数据存储篇1

目录 背景 1 kafka总体体系结构 2 疑问解答 2.1 高吞吐低延迟 2.2 实现分布式存储和数据读取 2.3 如何保证数据不丢失 背景 最近在和产品过项目审批的时候&#xff0c;深刻感受到业务方对系统的时时响应提出了更高的要求。目前手上大部分的业务都是基础定时任务去实现的&…

nodejs+vue高校会议室预订管理系统python-flask-django-php

伴随着我国社会的发展&#xff0c;人民生活质量日益提高。于是对系统进行规范而严格是十分有必要的&#xff0c;所以许许多多的信息管理系统应运而生。此时单靠人力应对这些事务就显得有些力不从心了。所以本论文将设计一套高校会议室预订管理系统&#xff0c;帮助学校进行会议…

JDK,JRE,JVM之间的关系

他们明面上的关系是JDK包含JRE&#xff0c;JRE包含JVM。 简单理解JDK就是Java开发工具包。JRE是Java运行环境。JVM是Java虚拟机。 JDK是面向开发者的&#xff0c;JRE是面向JAVA程序的用户的。也就是说开发者开发JAVA程序是需要用到JDK&#xff0c;如果用户不去开发JAVA程序&am…

【WPF应用10】基本控件-StackPanel:布局原理与实际应用

在Windows Presentation Foundation&#xff08;WPF&#xff09;中&#xff0c;布局是用户界面设计的核心部分&#xff0c;它决定了控件如何排列和空间如何分配。WPF提供了一系列布局面板&#xff08;Panel&#xff09;&#xff0c;以便开发者可以根据需要灵活地组织控件。在这…

OpenHarmony IDL工具规格及使用说明书(仅对系统应用开放)

IDL接口描述语言简介 当客户端和服务器进行IPC通信时&#xff0c;需要定义双方都认可的接口&#xff0c;以保障双方可以成功通信&#xff0c;OpenHarmony IDL&#xff08;OpenHarmony Interface Definition Language&#xff09;则是一种定义此类接口的工具。OpenHarmony IDL先…

掌握 Unity 中的状态机:综合指南

作者简介: 高科,先后在 IBM PlatformComputing从事网格计算,淘米网,网易从事游戏服务器开发,拥有丰富的C++,go等语言开发经验,mysql,mongo,redis等数据库,设计模式和网络库开发经验,对战棋类,回合制,moba类页游,手游有丰富的架构设计和开发经验。 (谢谢你的关注…

初识 Redis 浅谈分布式

目 录 一.认识 Redis二.浅谈分布式单机架构分布式是什么数据库分离和负载均衡理解负载均衡数据库读写分离引入缓存数据库分库分表引入微服务 三.概念补充四.分布式小结 一.认识 Redis 在 Redis 官网我们可以看到介绍 翻译过来就是&#xff1a;数以百万计的开发人员用作缓存、…

nodejs+vue高校社团管理小程序的设计与实现python-flask-django-php

相比于以前的传统手工管理方式&#xff0c;智能化的管理方式可以大幅降低学校的运营人员成本&#xff0c;实现了高校社团管理的标准化、制度化、程序化的管理&#xff0c;有效地防止了高校社团管理的随意管理&#xff0c;提高了信息的处理速度和精确度&#xff0c;能够及时、准…

t-rex2开放集目标检测

论文链接&#xff1a;http://arxiv.org/abs/2403.14610v1 项目链接&#xff1a;https://github.com/IDEA-Research/T-Rex 这篇文章的工作是基于t-rex1的工作继续做的&#xff0c;核心亮点&#xff1a; 是支持图片/文本两种模态的prompt进行输入&#xff0c;甚至进一步利用两…

CCF-CSP认证考试 202303-5 施肥 35/60/75/100分题解

更多 CSP 认证考试题目题解可以前往&#xff1a;CSP-CCF 认证考试真题题解 原题链接&#xff1a; 202303-5 施肥 时间限制&#xff1a; 2.0s 内存限制&#xff1a; 1.0GB 问题描述 春天到了&#xff0c;西西艾弗岛上的 n n n 块田地需要施肥了。 n n n 块田地编号为 1 , 2…

基于Google云原生工程师的kubernetes最佳实践(二)

目录 二、应用部署篇 为deployment打上丰富的label,以便selecting 使用sidecar容器部署agent、proxy等组件 使用init container处理依赖关系,而不要用sidecar 镜像tag使用版本号,不要用latest或空tag 为pod设置readiness和liveness探针 不要给所有服务都使用LoadBalance…

【微服务】以模块化单体架构开发微服务应用

目录 推荐超级课程: Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战我们知道,起初,单体应用有显著的优势:它们更容易开发和部署。从开发人员的角度来看,这种简单性是有益的。一切都是集中的,可以快速更新任何部分的业务逻辑并立即看到结果。这种开…

竞赛 python opencv 深度学习 指纹识别算法实现

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; python opencv 深度学习 指纹识别算法实现 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;4分创新点&#xff1a;4分 该项目较为新颖…