C++算法 —— 动态规划(9)完全背包问题

文章目录

  • 1、动规思路简介
  • 2、完全背包【模板】
  • 3、零钱兑换
  • 4、零钱兑换Ⅱ
  • 5、完全平方数


背包问题需要读者先明白动态规划是什么,理解动规的思路,并不能给刚接触动规的人学习。所以最好是看了之前的动规博客,以及01背包博客,才能看完全背包博客,或者你本人就已经懂得动规了。

1、动规思路简介

动规的思路有五个步骤,且最好画图来理解细节,不要怕麻烦。当你开始画图,仔细阅读题时,学习中的沉浸感就体验到了。

状态表示
状态转移方程
初始化
填表顺序
返回值

动规一般会先创建一个数组,名字为dp,这个数组也叫dp表。通过一些操作,把dp表填满,其中一个值就是答案。dp数组的每一个元素都表明一种状态,我们的第一步就是先确定状态。

状态的确定可能通过题目要求来得知,可能通过经验 + 题目要求来得知,可能在分析过程中,发现的重复子问题来确定状态。还有别的方法来确定状态,但都大同小异,明白了动规,这些思路也会随之产生。状态的确定就是打算让dp[i]表示什么,这是最重要的一步。状态表示通常用某个位置为结尾或者起点来确定。

状态转移方程,就是dp[i]等于什么,状态转移方程就是什么。像斐波那契数列,dp[i] = dp[i - 1] + dp[i - 2]。这是最难的一步。一开始,可能状态表示不正确,但不要紧,大胆制定状态,如果没法推出转移方程,没法得到结果,那这个状态表示就是错误的。所以状态表示和状态转移方程是相辅相成的,可以帮你检查自己的思路。

要确定方程,就从最近的一步来划分问题。

初始化,就是要填表,保证其不越界。像第一段所说,动规就是要填表。比如斐波那契数列,如果要填dp[1],那么我们可能需要dp[0]和dp[-1],这就出现越界了,所以为了防止越界,一开始就固定好前两个值,那么第三个值就是前两个值之和,也不会出现越界。初始化的方式不止这一点,有些问题,假使一个位置是由前面2个位置得到的,我们初始化最一开始两个位置,然后写代码,会发现不够高效,这时候就需要设置一个虚拟节点,一维数组的话就是在数组0位置处左边再填一个位置,整个dp数组的元素个数也+1,让原先的dp[0]变为现在的dp[1],二维数组则是要填一列和一行,设置好这一行一列的所有值,原先数组的第一列第一行就可以通过新填的来初始化,这个初始化方法在下面的题解中慢慢领会。

第二种初始化方法的注意事项就是如何初始化虚拟节点的数值来保证填表的结果是正确的,以及新表和旧表的映射关系的维护,也就是下标的变化。

填表顺序。填当前状态的时候,所需要的状态应当已经计算过了。还是斐波那契数列,填dp[4]的时候,dp[3]和dp[2]应当都已经计算好了,那么dp[4]也就出来了,此时的顺序就是从左到右。还有别的顺序,要依据前面的分析来决定。

返回值,要看题目要求。

背包问题有很多种分类,此篇是关于完全背包问题的,优化方法写在模板题中,此后都直接写在代码上。

2、完全背包【模板】

DP42 【模板】完全背包

在这里插入图片描述
在这里插入图片描述

和01背包不同的是,完全背包一个数可以选择多次。dp[i][j]表示从前i个物品中选,总体积不超过j,所有选法中最大的价值。

最后一个位置i,如果不选,那就看dp[i - 1][j],如果选1个,那就看dp[i - 1][j - v[i]] + w[i],如果选2个,那就看dp[i - 1][j - 2v[i]] + 2w[i],依次类推,这里只有j和后面的w变了,那么再按照这个式子写出dp[i][j - v[i]]的值,最后通过数学计算能得到dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i])。

初始化时,新增的一行一列全部为0。从上到下,从左到右填写,返回值是dp[n][V]。

接着第二问,再上面基础上做调整。 dp[i][j]表示体积必须等于j。按照之前的思路,dp[i][j] = -1来表示这个情况不存在,做不到要求。初始化时dp[0][0] = 0,第一行其余位置都是-1,第一列还是0。

#include <iostream>
#include <cstring>
using namespace std;const int N = 1010;int n, V, v[N], w[N];
int dp[N][N];int main()
{cin >> n >> V;for(int i = 1; i <= n; i++){cin >> v[i] >> w[i];}for(int i = 1; i <= n; i++){for(int j = 0; j <= V; j++){dp[i][j] = dp[i - 1][j];if(j >= v[i]) dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]);}}cout << dp[n][V] << endl;memset(dp, 0, sizeof(dp));for(int j = 1; j <= V; j++) dp[0][j] = -1;for(int i = 1; i <= n; i++){for(int j = 0; j <= V; j++){dp[i][j] = dp[i - 1][j];if(j >= v[i] && dp[i][j - v[i]] != -1)dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]);}}cout << (dp[n][V] == -1 ? 0 : dp[n][V]) << endl;return 0;
}

利用滚动数组做优化。和01背包不一样,它不需要从右到左来循环,它的一个位置需要同行的左边的一个位置和上方一个位置,同行这个位置得是更新后的。

#include <iostream>
#include <cstring>
using namespace std;const int N = 1010;int n, V, v[N], w[N];
int dp[N];int main()
{cin >> n >> V;for(int i = 1; i <= n; i++){cin >> v[i] >> w[i];}for(int i = 1; i <= n; i++){for(int j = v[i]; j <= V; j++){dp[j] = max(dp[j], dp[j - v[i]] + w[i]);}}cout << dp[V] << endl;memset(dp, 0, sizeof(dp));for(int j = 1; j <= V; j++) dp[j] = -1;for(int i = 1; i <= n; i++){for(int j = v[i]; j <= V; j++){if(dp[j - v[i]] != -1)dp[j] = max(dp[j], dp[j - v[i]] + w[i]);}}cout << (dp[V] == -1 ? 0 : dp[V]) << endl;return 0;
}

还有一个优化

    memset(dp, 0, sizeof(dp));for(int j = 1; j <= V; j++) dp[j] = -1;for(int i = 1; i <= n; i++){for(int j = v[i]; j <= V; j++){if(dp[j - v[i]] != -1)dp[j] = max(dp[j], dp[j - v[i]] + w[i]);}}cout << (dp[V] == -1 ? 0 : dp[V]) << endl;

这里有一个if判断,是为了能让这个dp值可用,再去让他+w[i]和dp[j]比较,我们可以让那个dp表中的每一个值足够小,这样即使dp[j - v[i]]参与了比较,也不会用它。

    memset(dp, 0, sizeof(dp));for(int j = 1; j <= V; j++) dp[j] = -0x3f3f3f3f;//INT_MIN的一半,也足够小。for(int i = 1; i <= n; i++){for(int j = v[i]; j <= V; j++){dp[j] = max(dp[j], dp[j - v[i]] + w[i]);}}cout << (dp[V] < 0 ? 0 : dp[V]) << endl;

3、零钱兑换

322. 零钱兑换

在这里插入图片描述

dp[i][j]表示从前i个硬币中选,总金额正好等于j,所有的选法中,最少的硬币个数,j就是amount。

最后一个位置i,不选的话就看dp[i - 1][j]。如果选i,选1个,那么这个硬币价值就是coins[i],那为了能正好达到j而不超过,那就得看dp[i - 1][j - coins[i]],然后+1,因为存的是个数,如果选2个,那么就是dp[i - 1][j - 2coins[i]] + 2,根据之前的数学计算,选i的情况就是dp[i][j - coins[i]] + 1,然后和dp[i - 1][j]取小就行。

初始化时多加一行一列,dp[0][0]是0,第一行其它位置是无效的,因为没有硬币的话就不可能凑成金额,按照之前的优化,本来设置成-1然后判断,这里就初始化成足够大的数字就行,因为这道题就min,初始化成0x3f3f3f3f。

返回最后一个位置的值,但有可能到最后也凑不成,所以最后要判断一下。以及滚动数组优化。

    int coinChange(vector<int>& coins, int amount) {const int INF = 0x3f3f3f3f;int n = coins.size();vector<int> dp(amount + 1, INF);dp[0] = 0;for(int i = 1; i <= n; i++){for(int j = coins[i - 1]; j <= amount; j++){dp[j] = min(dp[j], dp[j - coins[i - 1]] + 1);}}return dp[amount] >= INF ? -1 : dp[amount];}

4、零钱兑换Ⅱ

518. 零钱兑换 II

在这里插入图片描述

看了上一个题的思路,这题很快就出来答案了。

上一个题代码

    int coinChange(vector<int>& coins, int amount) {const int INF = 0x3f3f3f3f;int n = coins.size();vector<int> dp(amount + 1, INF);dp[0] = 0;for(int i = 1; i <= n; i++){for(int j = coins[i - 1]; j <= amount; j++){dp[j] = min(dp[j], dp[j - coins[i - 1]] + 1);}}return dp[amount] >= INF ? -1 : dp[amount];}

现在要求组合数,所以选i的情况中就不需要+1了。不用求min,而是所有可能的数值加起来,按照优化后的代码,dp[j] = dp[j] + dp[j - coins[i]],返回时不需要判断,直接返回最后一个位置的值即可。

    int change(int amount, vector<int>& coins) {int n = coins.size();vector<int> dp(amount + 1);dp[0] = 1;for(int i = 1; i <= n; i++){for(int j = coins[i - 1]; j <= amount; j++){dp[j] += dp[j - coins[i - 1]];}}return dp[amount];}

5、完全平方数

279. 完全平方数

在这里插入图片描述

也是一个完全背包问题,dp[i][j]表示从前i个完全平方数中挑选,总和正好等于j,所有选法中,最小的数量。

从1到i方的区间来分析,不选i方,那就看dp[i - 1][j],选一个i方,那就看dp[i - 1][j - i^2] + 1,选两个i方,和之前的一样,最后就是dp[i][j - i^2] + 1,然后两个数取min。

初始化,dp[0][0]是0,第一行其余位置都是不存在的,所以也弄成特殊值0x3f3f3f3f,第一列不用管,默认为0就行。

返回值是dp[根号n][n]。

    int numSquares(int n) {int m =sqrt(n);vector<int> dp(n + 1, 0x3f3f3f3f);dp[0] = 0;for(int i = 1; i <= m; i++){for(int j = i * i; j <= n; j++){dp[j] = min(dp[j], dp[j - i * i] + 1);}}return dp[n];}

结束。

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

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

相关文章

Vue11 计算属性

先看不用计算属性的两种方法 插值语法 简单的计算&#xff0c;可以使用插值语法 <!DOCTYPE html> <html><head><meta charset"UTF-8" /><title>姓名案例_插值语法实现</title><!-- 引入Vue --><script type"te…

软件测试基础学习

注意&#xff1a; 各位同学们&#xff0c;今年本人求职目前遇到的情况大体是这样了&#xff0c;开发太卷&#xff0c;学历高的话优势非常的大&#xff0c;公司会根据实际情况考虑是否值得培养&#xff08;哪怕技术差一点&#xff09;&#xff1b;学历稍微低一些但是技术熟练的…

MATLAB算法实战应用案例精讲-【优化算法】A*算法

前言 A*算法最早于1964年在IEEE Transactions on Systems Science and Cybernetics中的论文《A Formal Basis for the Heuristic Determination of Minimum Cost Paths》中首次提出。 其属于一种经典的启发式搜索方法,所谓启发式搜索,就在于当前搜索结点往下选择下一步结点时…

SpringMVC处理请求核心流程

一、前言 SpringMVC是一个基于Java的Web框架&#xff0c;它使用MVC&#xff08;Model-View-Controller&#xff09;设计模式来处理Web请求。在SpringMVC中&#xff0c;请求处理的核心流程主要包括以下几个步骤&#xff1a; 1、用户发送请求到前端控制器&#xff08;Dispatche…

Linux:minishell

目录 1.实现逻辑 2.代码及效果展示 1.打印字符串提示用户输入指令 2.父进程拆解指令 3.子进程执行指令,父进程等待结果 4.效果 3.实现过程中遇到的问题 1.打印字符串的时候不显示 2.多换了一行 3.cd路径无效 4.优化 1.ll指令 2.给文件或目录加上颜色 代码链接 模…

k8s集群的简单搭建

K8S简单集群搭建 前提条件 windos11电脑&#xff0c;内存16g以上安装vmware虚拟机软件安装三个centos7虚拟机&#xff0c;分配硬盘40g,内存4g,CPU4核心网络均采用NAT模式&#xff08;新建虚拟机默认的模式&#xff09; centos7镜像下载&#xff1a;https://mirrors.tuna.tsi…

Scala第十三章节

Scala第十三章节 1. 高阶函数介绍 2. 作为值的函数 3. 匿名函数 4. 柯里化 5. 闭包 6. 控制抽象 7. 案例: 计算器 scala总目录 文档资料下载

MacOS怎么配置JDK环境变量

1 输入命令看是否配置了JDk 的环境变量&#xff1a;echo $JAVA_HOME 要是什么也没输出 证明是没配置 2 输入命令编辑 sudo vim ~/.bash_profile 然后按 i &#xff0c;进入编辑模式&#xff0c;粘贴下面的代码&#xff0c;注意&#xff1a;JAVA_HOME后面路径需要改成自己的版…

mstsc无法保存RDP凭据, 100%生效

问题 即使如下两项都打勾&#xff0c;其还是无法保存凭据&#xff0c;特别是连接Ubuntu (freerdp server)&#xff1a; 解决方法 网上多种复杂方法&#xff0c;不生效&#xff0c;其思路是修改后台配置&#xff0c;以使mstsc跟平常一样自动记住凭据。最后&#xff0c;如下的…

CentOS7安装Oracle XE记录

本文仅是CentOS7安装Oracle XE记录&#xff0c;供参考 1、下载安装包 oracle-xe-11.2.0-1.0.x86_64.rpm.zip 2、安装 &#xff08;1&#xff09;第一次安装 [rootnode1 opt]# cd Disk1/ [rootnode1 Disk1]# ll 总用量 309884 -rw-r--r-- 1 root root 317320273 9月 28 09…

Android悬浮窗框架

官网 项目地址&#xff1a;Github博客地址&#xff1a;悬浮窗需求终结者 本框架意在解决一些悬浮窗的需求&#xff0c;如果是普通的 Toast 封装推荐使用Toaster 集成步骤 如果你的项目 Gradle 配置是在 7.0 以下&#xff0c;需要在 build.gradle 文件中加入 allprojects {…

2022 年安徽省职业院校技能大赛高职组“软件测试”赛项竞赛任务书

2022 年安徽省职业院校技能大赛高职组 “软件测试”赛项竞赛任务书 有问题可私信我 2022 年 10 月 一、竞赛时间、内容及成绩组成 &#xff08;一&#xff09;竞赛时间 本次竞赛时间共为 5 小时&#xff0c;参赛选手自行安排任务进度&#xff0c;休息、饮水、如厕等不设专门用时…

281_JSON_两段例子的比较,哪一段更简洁、易懂、没有那么多嵌套

《第一份:》//组装Notificationif (bSendAINotification){BOOST_AUTO(iter_flashnotification, documentAll.FindMember("Notification"));if (iter_flashnotification != documentAll.

ARM---实现1-100求和任务

.text .globl _start_start:mov r0, #0x1mov r1, #0x1 给r1加一固定1不变mov r2, #0x64 100判断bl sumcmp r1, r2 sum:addcc r1, r1,#0x1 r1自增addcc r0, r0, r1 r0求和movcc pc,lrstop:b stop.end

day49数据库 索引 事务

一、索引 什么是索引&#xff1a;索引是数据库库中用来提高查询效率的技术&#xff0c;类似于目录 为什么要使用索引&#xff1a;如果不使用索引&#xff0c;数据会零散的保存在磁盘块中&#xff0c;查询数据需要遍历每一个磁盘块&#xff0c;直到找到数据为止&#xff0c;效率…

linux python 保存图形savefig import matplotlib.pyplot as plt

import matplotlib.pyplot as plt # 绘制图形 mod.plot_history(20)# 保存图形 plt.savefig("my_training_ephoes_plot.png") # 保存为PNG格式 # 保存图形并设置dpi参数 plt.savefig("my_plot.png", dpi600) # 保存为PNG格式&#xff0c;设置dpi为300

力扣 -- 115. 不同的子序列

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int numDistinct(string s, string t) {int ns.size();int mt.size();//多开一行&#xff0c;多开一列vector<vector<double>> dp(m1,vector<double>(n1));for(size_t j0;j<n;j){dp[…

Python基础语法(2)

目录 一、顺序语句 二、条件语句 2.1 什么是条件语句 2.2 语法格式 2.1.1 if 2.2.2 if - else 2.2.3 if - elif - else 2.2.4 缩进和代码块 2.2.5 练习 1) 输入一个整数&#xff0c;判定是否是奇数 2) 输入一个整数&#xff0c; 判定是正数还是负数 3) 判定年份是否…

C#餐饮收银系统

一、引言 餐饮收银系统是一种用于管理餐馆、咖啡厅、快餐店等餐饮业务的计算机化工具。它旨在简化点餐、结账、库存管理等任务&#xff0c;提高运营效率&#xff0c;增强客户体验&#xff0c;同时提供准确的财务记录。C# 餐饮收银系统是一种使用C#编程语言开发的餐饮业务管理软…

Flink Data Source

Flink Data Source 一、内置 Data Source Flink Data Source 用于定义 Flink 程序的数据来源,Flink 官方提供了多种数据获取方法,用于帮助开发者简单快速地构建输入流,具体如下: 1.1 基于文件构建 1. readTextFile(path):按照 TextInputFormat 格式读取文本文件,并将…