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

news2024/12/25 12:29:12

文章目录

  • 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.coloradmin.cn/o/1059541.html

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

相关文章

学习C++语言可以适用于哪些方面

学习C可以让你具备开发各种类型软件和系统的能力&#xff0c;它是一种通用的、高性能的编程语言。以下是学习C的一些用途和应用领域&#xff1a; 系统开发&#xff1a;C被广泛用于操作系统、驱动程序和嵌入式系统的开发。通过学习C&#xff0c;你可以编写底层的系统代码&#x…

java大富翁

一、 概述 Java Swing大富翁游戏是一个经典的大富翁桌面游戏的简单实现&#xff0c;使用Java Swing库创建。该游戏允许玩家在一个虚拟棋盘上掷骰子&#xff0c;购买和升级属性&#xff0c;赚取租金和尽量丰富自己。这个文档说明将介绍如何安装和运行游戏&#xff0c;以及游戏规…

【C++】C++11——右值引用和移动语义、左值引用和右值引用、右值引用使用场景和意义、完美转发、新的类功能

文章目录 C115.右值引用和移动语义5.1左值引用和右值引用5.2左值引用与右值引用比较5.3右值引用使用场景和意义5.4右值引用引用左值及其一些更深入的使用场景分析5.5完美转发 6.新的类功能 C11 5.右值引用和移动语义 右值引用是C11引入的一个新特性&#xff0c;用于支持移动语义…

冯诺依曼体系结构与进程的初步理解

目录 一&#xff0c;冯诺依曼体系结构 1.是什么&#xff1f;特点 2.为什么&#xff1f; 二&#xff0c;操作系统 三&#xff0c;进程 1.什么是进程&#xff1f; 2.查看进程 3.进程的管理 4.fork()创建子进程 1.fork()简介 2.fork()干了啥 3.fork()为什么会有两个返回…

【Java】微服务——Ribbon负载均衡(跟进源码分析原理)

添加LoadBalanced注解&#xff0c;即可实现负载均衡功能&#xff0c;这是什么原理 1.负载均衡原理 SpringCloud底层其实是利用了一个名为Ribbon的组件&#xff0c;来实现负载均衡功能的。 2.源码跟踪 为什么我们只输入了service名称就可以访问了呢&#xff1f;之前还要获取…

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

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

Python无废话-办公自动化Excel写入操作

Python 办公自动化-Excel写入 创建并保存Excel文件 import openpyxl workbookopenpyxl.Workbook() #创建空Excel文件 sheetworkbook.active #获取活动的工作表 sheet.title“测试“ #修改sheet工作表名称为测试 workbook.save(“data\input\Test.xlsx”) #保存Excel文件 …

R中的min()函数 和max()函数

通过min()函数和max()函数产生Inf 数值空集的最小值和最大值是Inf和–Inf(按此顺序&#xff01;)这确保了传递性&#xff0c;例如min(x1&#xff0c;min(x2)) min(x1&#xff0c;x2)。对于数值x&#xff0c;每当length (x) 0时&#xff0c;max(x) - Inf和min(x) Inf(如果需…

C#餐饮收银系统

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

SDK Vitis记录

文章目录 SDK记录SDK中报错“undefined reference to sqrt”的解决方法通过XML文件导入工程的include路径方法说明 其他设置编译选项设置某些文件/文件夹不编译单独设置文件的编译选项 向存储区中导入/导出数据通过GUI操作使用命令行操作 产生C代码的MAP文件在Xilinx SDK 工程的…

Golang 中的调试技巧

掌握有效的策略和工具&#xff0c;实现顺畅的开发 调试是每位开发人员都必须掌握的关键技能。它是识别、隔离和解决代码库中问题的过程。在 Golang 的世界中&#xff0c;掌握有效的调试技巧可以显著提升您的开发工作流程&#xff0c;并帮助您创建更可靠和健壮的应用程序。在本…

C语言 —— 函数栈帧的创建和销毁

在我们之前学习函数的时候&#xff0c;我们可能有很多困惑? 比如: 局部变量是怎么创建的?为什么局部变量的值是随机值?函数是怎么传参的?传参的顺序是怎样的?形参和实参是什么关系?函数调用是怎么做的?函数调用是结束后怎么返回的? 那么要解决这些问题, 我们就需要知道…

Raspberry Pi 5 新平台 新芯片组

Raspberry Pi 5 的 CPU 和 GPU 性能提高了两到三倍&#xff1b;内存和 I/O 带宽大约是两倍&#xff1b;并且是首款采用英国剑桥内部设计的芯片的 Raspberry Pi 计算机&#xff0c;4GB 型号的售价为 60 美元&#xff0c;8GB 版本的售价为 80 美元 主要特点包括&#xff1a; 2.4…

Zama的fhEVM:基于全同态加密实现的隐私智能合约

1. 引言 Zama的fhEVM定位为&#xff1a; 基于全同态加密实现的隐私智能合约 解决方案 开源代码见&#xff1a; https://github.com/zama-ai/fhevm&#xff08;TypeScript Solidity&#xff09; Zama的fhEVM协议中主要包含&#xff1a; https://github.com/zama-ai/tfhe-…

Windows11+VS2022+OCCT7.6.0安装配置记录

Windows11VS2022OCCT7.6.0安装配置记录 工具及源码准备VS2022以及CMake下载OCCT源码下载第三方库 CMake修改occt_toolkit.cmake进行CMake Visual Studio环境配置配置包含目录配置库目录配置链接器设置系统环境变量配置项目调试环境环境测试 其他方法 主要参考此文&#xff0c;在…

自然语言处理的分类

动动发财的小手&#xff0c;点个赞吧&#xff01; 简介 作为理解、生成和处理自然语言文本的有效方法&#xff0c;自然语言处理&#xff08;NLP&#xff09;的研究近年来呈现出快速传播和广泛采用。鉴于 NLP 的快速发展&#xff0c;获得该领域的概述并对其进行维护是很困难的。…

Golang 语言学习 01 包含如何快速学习一门新语言

Golang方向 区块链 go服务器端 (后台流量支撑程序) 支撑主站后台流量&#xff08;排序&#xff0c;推荐&#xff0c;搜索等&#xff09;&#xff0c;提供负载均衡&#xff0c;cache&#xff0c;容错&#xff0c;按条件分流&#xff0c;统计运行指标 (qps&#xff0c; latenc…

java飞机大战

一、 概述 1.1 项目简介 本次Java课程设计是做一个飞机大战的游戏&#xff0c;应用Swing编程&#xff0c;完成一个界面简洁流畅、游戏方式简单&#xff0c;玩起来易于上手的桌面游戏。该飞机大战项目运用的主要技术即是Swing编程中的一些窗口类库、事件监听以及贴图技术。 1…

微信小程序WebSocket实现stream流式聊天对话功能

要在微信小程序实现聊天对话功能&#xff0c;回话是流式应答&#xff0c;这里使用了WebSocket技术。WebSocket大家应该都很熟悉&#xff0c;使用wx.connectSocket就可以了。这里可能需要注意下的是流式应答&#xff0c;后端如何发送&#xff0c;前端如何接收。直接上代码&#…

【1】c++设计模式——>UML类图的画法

UML介绍 UML:unified modeling language 统一建模语言 面向对象设计主要就是使用UML类图&#xff0c;类图用于描述系统中所包含的类以及他们之间的相互关系&#xff0c;帮助人们简化对系统的理解&#xff0c;他是系统分析和设计阶段的重要产物&#xff0c;也是系统编码和测试的…