【动态规划】背包问题 {01背包问题;完全背包问题;二维费用背包问题}

一、背包问题概述

背包问题(Knapsackproblem)是⼀种组合优化的NP完全问题。

问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最⾼。

根据物品的个数,分为如下几类:

  • 01背包问题:每个物品只有⼀个
  • 完全背包问题:每个物品有⽆限多个
  • 多重背包问题:每件物品最多有si个
  • 混合背包问题:每个物品会有上⾯三种情况…
  • 分组背包问题:物品有n组,每组物品里有若干个,每组里最多选一个物品

其中上述分类里面,根据背包是否装满,⼜分为两类:

  • 不⼀定装满背包
  • 背包⼀定装满

优化⽅案:

  • 空间优化-滚动数组
  • 单调队列优化
  • 贪⼼优化

根据限定条件的个数,又分为两类:

  • 限定条件只有⼀个:比如体积->普通的背包问题
  • 限定条件有两个:比如体积+重量->⼆维费⽤背包问题

根据不同的问法,又分为很多类:

  • 输出⽅案
  • 求⽅案总数
  • 最优⽅案
  • ⽅案可⾏性

其实还有很多分类,但是我们仅需了解即可。
因此,背包问题种类非常繁多,题型非常丰富,难度也是非常难以捉摸。但是,尽管种类非常多,都是从01背包问题演化过来的。所以,⼀定要把01背包问题学好。


二、相关编程题

2.1 01背包问题

2.1.1 01背包(模板)

题目链接

【模板】01背包_牛客题霸_牛客网 (nowcoder.com)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

#include <iostream>
#include <cstring>
using namespace std;
#define N 1010

int n, V;
int v[N], w[N];
int dp[N][N];

int main() {
    //读入数据
    cin >> n >> V;
    for(int i = 1; i <= n; ++i) //注意所有下标从1开始
    {
        cin >> v[i] >> w[i];
    }

    //解决第一问
    for(int i = 1; i <= n; ++i)
    {
        for(int j = 1; j <= V; ++j)
        {
            dp[i][j] = dp[i-1][j]; // 不选i物品
            if(j>=v[i]) //要保证不越界,有空间存放v[i]
                dp[i][j] = max(dp[i][j], w[i]+dp[i-1][j-v[i]]); //选i物品
        }
    }
    cout << dp[n][V] << endl;

    //解决第二问
    memset(dp, 0, sizeof(dp));
    for(int j = 1; j <= V; ++j) dp[0][j] = -1; //-1表示恰好装满体积j无解

    for(int i = 1; i <= n; ++i)
    {
        for(int j = 1; j <= V; ++j)
        {
            dp[i][j] = dp[i-1][j]; // 不选i物品
            if(j>=v[i] && dp[i-1][j-v[i]]!=-1) //条件1保证不越界,有空间存放v[i];条件2保证恰好装满
                dp[i][j] = max(dp[i][j], w[i]+dp[i-1][j-v[i]]); //选i物品
        }
    }
    cout << (dp[n][V]==-1? 0:dp[n][V]) << endl;
}

//空间优化
int dp[N];

int main() {
    //读入数据...

    //解决第一问
    for(int i = 1; i <= n; ++i)
    {
        for(int j = V; j>=v[i]; --j) //注意从右往左遍历
                dp[j] = max(dp[j], w[i]+dp[j-v[i]]);
    }
    cout << dp[V] << endl;

    //解决第二问
    memset(dp, 0, sizeof(dp));
    for(int j = 1; j <= V; ++j) dp[j] = -1; //-1表示恰好装满体积j无解
    for(int i = 1; i <= n; ++i)
    {
        for(int j = V; j>=v[i]; --j)
        {
            if(dp[j-v[i]]!=-1)
                dp[j] = max(dp[j], w[i]+dp[j-v[i]]);
        }
    }
    cout << (dp[V]==-1? 0:dp[V]) << endl;
}

2.1.2 分割等和子集

题目链接

416. 分割等和子集 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

  • 01背包必须装满
  • 为什么不用-1标识无解?因为其状态表示中自带无解判断(false)

编写代码

//空间优化
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int n = nums.size();
        int sum = 0;
        for(auto e : nums) sum+=e;
        if(sum%2==1) return false;
        vector<bool> dp(sum/2+1);
        dp[0] = true;
        for(int i = 1; i <= n; ++i)
        {
            for(int j = sum/2; j >= nums[i-1]; --j)
                dp[j] = dp[j] || dp[j-nums[i-1]];
        }
        return dp[sum/2];
    }
};

2.1.3 目标和

题目链接

494. 目标和 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

  • 01背包必须装满
  • 为什么不用-1标识无解?因为其状态表示中自带无解判断(0种选法)

编写代码

//空间优化
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int n = nums.size();
        int sum = 0;
        for(auto e : nums) sum+=e;
        int a = (sum+target)/2;
        if(a<0 || (sum+target)%2==1) return false; //a是所有正数的和;必须是偶数能被2整除;
        vector<int> dp(a+1);
        dp[0] = 1;
        for(int i = 1; i <= n; ++i)
        {
            for(int j = a; j >= nums[i-1]; --j)
                dp[j] += dp[j-nums[i-1]];
        }
        return dp[a];
    }
};

2.1.4 最后一块石头的重量Ⅱ

题目链接

1049. 最后一块石头的重量 II - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

  • 01背包不必装满

编写代码

//空间优化
class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int n = stones.size();
        int sum = 0;
        for(auto e : stones) sum+=e;
        int aim = sum/2;
        vector<int> dp(aim+1);
        for(int i = 1; i <= n; ++i)
        {
            for(int j = aim; j >=stones[i-1]; --j)
                dp[j] = max(dp[j], stones[i-1]+dp[j-stones[i-1]]);
        }
        return sum-dp[aim]*2;
    }
};

2.2 完全背包问题

2.2.1 完全背包(模板)

题目链接

【模板】完全背包_牛客题霸_牛客网 (nowcoder.com)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

和01背包唯一的不同点就是每个物品可以选择无数次,因此在选择i物品的状态转移方程中有所变化。

提示:综合了01背包、通配符匹配、正则表达式匹配的考点

编写代码

#include <iostream>
#include <cstring>
using namespace std;

const int N = 1010;
int n, V;
int 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 = 1; 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 = 1; 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;
}

//空间优化
int dp[N];

int main() {
    //读入数据...
    
    //解决第一问
    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;
}


2.2.2 零钱兑换

题目链接

322. 零钱兑换 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

提示:不能再像完全背包那样用-1表示无解。因为这里求的是最小值,如果使用-1,而不选i位置无解,即使选择i位置的零钱有解,也会在取min时取到-1。实际上我们设置无解为-1的初衷就是为了不参与后续的比较,因此可以用INF(无穷大)表示无解,这样的话,在取min时永远不会取到INF。

编写代码

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        const int INF = 0x3f3f3f3f;
        int n = coins.size();
        vector<vector<int>> dp(n+1, vector<int>(amount+1));
        for(int j = 1; j <= amount; ++j) 
            dp[0][j] = INF;

        for(int i = 1; i <= n; ++i)
        {
            for(int j = 1; j <= amount; ++j)
            {
                dp[i][j] = dp[i-1][j];
                if(j >= coins[i-1])
                    dp[i][j] = min(dp[i][j], dp[i][j-coins[i-1]]+1);
            }
        }
        return dp[n][amount]>=INF? -1:dp[n][amount];
    }
};

//空间优化
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        const int INF = 0x3f3f3f3f;
        int n = coins.size();
        vector<int> dp(amount+1);
        for(int j = 1; j <= amount; ++j) 
            dp[j] = INF;

        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];
    }
};

2.2.3 零钱兑换Ⅱ

题目链接

518. 零钱兑换 II - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

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

2.2.4 完全平方数

题目链接

279. 完全平方数 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

class Solution {
public:
    int numSquares(int n) {
        int m = sqrt(n);
        const int INF = 0x3f3f3f3f;
        vector<int> dp(n+1, INF);
        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];
    }
};

2.3 二维费用背包问题

2.3.1 一和零

题目链接

474. 一和零 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

  • 01背包,不必装满,二维费用

编写代码

//三维dp表
class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int cnt = strs.size();
        vector<vector<vector<int>>> dp(cnt+1, vector<vector<int>>(m+1, vector<int>(n+1)));
        for(int i = 1; i <= cnt; ++i)
        {
            //统计以下字符串中0,1的个数
            int c0 = Count0(strs[i-1]);
            int c1 = strs[i-1].size()-c0;
            for(int j = 0; j <= m; ++j)
                for(int k = 0; k <= n; ++k)
                {
                    dp[i][j][k] = dp[i-1][j][k]; //不选i位置的字符串
                    if(j>=c0 && k>=c1) //选i位置的字符串
                        dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-c0][k-c1]+1);
                }
        }
        return dp[cnt][m][n];
    }

    int Count0(const string& str)
    {
        int cnt = 0;
        for(auto ch : str)
            if(ch == '0') ++cnt;
        return cnt;
    }
};

//空间优化:二维dp表
class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int cnt = strs.size();
        vector<vector<int>> dp(m+1, vector<int>(n+1));
        for(int i = 1; i <= cnt; ++i)
        {
            int c0 = Count0(strs[i-1]);
            int c1 = strs[i-1].size()-c0;
            for(int j = m; j >= c0; --j) //注意从大到小遍历
                for(int k = n; k >= c1; --k)
                {
                    dp[j][k] = max(dp[j][k], dp[j-c0][k-c1]+1);
                }
        }
        return dp[m][n];
    }
};

2.3.2 盈利计划

题目链接

879. 盈利计划 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

提示:k可以小于p[i],表示单单i项计划的盈利就超过了最低限度k。但是作为数组的下标k-p[i]不能是负数,因此我们采取一个折中的方案,当k-p[i]<0时从前i-1项计划中选择的总利润最低为0即可。

编写代码

//三维dp表
class Solution {
public:
    int profitableSchemes(int n, int m, vector<int>& group, vector<int>& profit) {
        int cnt = group.size();
        vector<vector<vector<int>>> dp(cnt+1, vector<vector<int>>(n+1, vector<int>(m+1)));
        for(int j = 0; j <= n; ++j) dp[0][j][0] = 1; //没有计划,没有利润,无论多少人数都可以选空
        for(int i = 1; i <= cnt; ++i)
        {
            for(int j = 0; j <= n; ++j)
            {
                for(int k = 0; k <= m; ++k)
                {
                    dp[i][j][k] = dp[i-1][j][k];
                    if(j>=group[i-1])
                        dp[i][j][k] += dp[i-1][j-group[i-1]][max(0, k-profit[i-1])];
                    dp[i][j][k] %= (int)1e9+7;
                }
            }
        }
        return dp[cnt][n][m];
    }
};

//空间优化:二维dp表
class Solution {
public:
    int profitableSchemes(int n, int m, vector<int>& group, vector<int>& profit) {
        int cnt = group.size();
        vector<vector<int>> dp(n+1, vector<int>(m+1));
        for(int j = 0; j <= n; ++j) dp[j][0] = 1;
        for(int i = 1; i <= cnt; ++i)
        {
            for(int j = n; j >= group[i-1]; --j)
                for(int k = m; k >= 0; --k)
                {
                    dp[j][k] += dp[j-group[i-1]][max(0, k-profit[i-1])];
                    dp[j][k] %= (int)1e9+7;
                }
        }
        return dp[n][m];
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【精品资料】模块化数据中心解决方案(33页PPT)

引言&#xff1a;模块化数据中心解决方案是一种创新的数据中心设计和部署策略&#xff0c;旨在提高数据中心的灵活性、可扩展性和效率。这种方案通过将数据中心的基础设施、计算、存储和网络资源封装到标准化的模块中&#xff0c;实现了快速部署、易于管理和高效运维的目标 方案…

数据库管理-第218期 服务器内存(20240711)

数据库管理218期 2024-07-11 数据库管理-第218期 服务器内存&#xff08;20240711&#xff09;1 内存2 ECC内存3 原理3.1 多副本传输3.2 纠错码3.3 汉明码 总结 数据库管理-第218期 服务器内存&#xff08;20240711&#xff09; 作者&#xff1a;胖头鱼的鱼缸&#xff08;尹海文…

【边缘计算网关教程】4.西门子PPI协议对接

前景回顾&#xff1a;【边缘计算网关教程】3.创建第二个流程-CSDN博客 目录 1. 硬件连接 2. PLC串口参数 2.1. 打开STEP7软件 2.2. 查看通信参数 3. 网关设置 3.1. PLC连接设置 3.2. 数据点位设置 3.3. 测试 西门子 PPI 协议 适配PLC&#xff1a;S7-200 西门子S7-200 PLC…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(七)-通过无人机实现无线接入的独立部署

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…

FPGA入门-自用

写代码&#xff0c;并将引脚对应到板子相应的引脚上 下载程序到板子上 遇到错误了&#xff0c;不按想的来的了&#xff0c;进行仿真 查看网表图查看问题所在 简化了一些步骤&#xff1a;未使用引脚的设置&#xff0c;电压设置&#xff1b; 通过画网表结构图来构成电路 时钟 …

JVM系列 | 对象的创建与存储

JVM系列 | 对象的生命周期1 对象的创建与存储 文章目录 前言对象的创建过程内存空间的分配方式方式1 | 指针碰撞方式2 | 空闲列表 线程安全问题 | 避免空间冲突的方式方式1 | 同步处理&#xff08;加锁)方式2 | 本地线程分配缓存 对象的内存布局Part1 | 对象头Mark Word类型指针…

SQl server 日期函数查询相关练习

练习1.按月份分析销售数据。 create database date_db; use date_db; CREATE TABLE SalesData ( SaleID INT PRIMARY KEY IDENTITY(1,1), ProductName NVARCHAR(100) NOT NULL, SaleAmount DECIMAL(10, 2) NOT NULL, SaleDate DATE NOT NULL ); INSERT INTO Sa…

mybatis语法进阶1

日志的使用 我们在使用MyBatis的时候, 其实MyBatis框架会打印一些必要的日志信息, 在开发阶段这些日志信息对我们分析问题,理解代码的执行是特别有帮助的; 包括项目上线之后,我们也可以收集项目的错误日志到文件里面去; 所以我们采用专门的日志系统来处理. 步骤 导入坐标拷贝…

算法类学习笔记 ———— 图像金字塔

文章目录 介绍分类高斯金字塔&#xff08;向下采样&#xff09;构建步骤差分高斯金字塔 拉普拉斯金字塔 介绍 图像金字塔是图像中多尺度表达的一种&#xff0c;最主要用于图像的分割&#xff0c;是一种以多分辨率来解释图像的有效但概念简单的结构。图像金字塔最初用于机器视觉…

如何在Mac上恢复已删除的存档文件?

在本文中&#xff0c;我们将分享在 macOS 或 OS X 上运行的 MacBook、iMac 或 Mac mini 上恢复已删除存档文件的不同方法。 下载免费试用的 Mac 数据恢复软件以在 Mac 上恢复已删除的存档文件。 macOS 可以选择压缩您的文件。您只需选择文件&#xff0c;按住 Control 键单击&a…

【排序 - 插入排序 和 希尔排序】

插入排序&#xff08;Insertion Sort&#xff09;是一种简单直观的排序算法&#xff0c;它的工作原理是逐步构建有序序列。在排序过程中&#xff0c;它将未排序的元素逐个插入到已排序的部分中&#xff0c;从而在每次插入时扩展已排序序列的长度。 原理介绍 插入排序的基本思…

【流媒体】 通过ffmpeg硬解码拉流RTSP并播放

简介 目前RTSP拉流是网络摄像头获取图片数据常用的方法&#xff0c;但通过CPU软解码的方式不仅延时高且十分占用资源&#xff0c;本文提供了一种从网络摄像头RTSP硬解码的拉流的方法&#xff0c;并且提供python代码以便从网络摄像头获取图片进行后续算法处理。 下载ffmpeg F…

ArcGIS识别不GDB文件地理数据库显示为空?

​ 点击下方全系列课程学习 点击学习—>ArcGIS全系列实战视频教程——9个单一课程组合系列直播回放 点击学习——>遥感影像综合处理4大遥感软件ArcGISENVIErdaseCognition 我们经常会碰到拷贝的GDB文件ArcGIS无法识别&#xff0c;软件只是把他当做普通的文件夹去看待&am…

主机安全-进程、命令攻击与检测

目录 概述反弹shell原理nc/dev/xxx反弹shell下载不落地反弹Shell各种语言反弹shell linux提权sudosuid提权mysql提权 Dnslog参考 概述 本文更新通过在主机&#xff08;不含容器&#xff09;上直接执行命令或启动进程来攻击的场景。检测方面以字节跳动的开源HIDS elkeid举例。每…

滑块拼图验证码识别

通常滑块验证码都是横向滑动&#xff0c;今天看到一个比较特别的滑块拼图验证码&#xff0c;他不仅能在横向上滑动&#xff0c;还需要进行纵向滑动。如下图所示&#xff1a; 他的滑块在背景图片的左上角&#xff0c;需要鼠标拖动左上角的滑块&#xff0c;移动到背景图的缺口位置…

Apache Doris:下一代实时数据仓库

Apache Doris&#xff1a;下一代实时数据仓库 概念架构设计快速的原因——其性能的架构设计、特性和机制基于成本的优化器面向列的数据库的快速点查询数据摄取数据更新服务可用性和数据可靠性跨集群复制多租户管理便于使用半结构化数据分析据仓一体分层存储 词条诞生技术概述适…

探索性数据分析:使用Python与Pandas库实现数据洞察

探索性数据分析&#xff1a;使用Python与Pandas库实现数据洞察 引言 在当今数据驱动的时代&#xff0c;数据分析已成为决策制定、策略规划和业务优化的关键环节。无论是商业智能、金融分析还是市场研究&#xff0c;数据分析都扮演着至关重要的角色。Pandas库作为Python生态系统…

html02-标签继续学习

1.列表标签 1.1 列表标签的使用场景 场景&#xff1a;在网页中按照 行 展示关联性的内容&#xff0c;如&#xff1a;新闻列表、排行榜、账单等 特点&#xff1a;按照行的方式&#xff0c;整齐显示内容 种类&#xff1a;无序列表、有序列表、自定义列表 1.2无序列表 <!--…

0.单片机工作原理

文章目录 最小系统 单片机芯片 时钟电路 复位电路 电源 最小系统 单片机芯片 本次51单片机的芯片为&#xff1a;STC89C52 Flash(闪存)程序存储器&#xff1a;存储程序的空间 SRAM&#xff1a;数据存储器&#xff0c;可用于存放程序执行的中间结果和过程数据 DPTR&#xff1a;…

中间件——Kafka

两个系统各自都有各自要去做的事&#xff0c;所以只能将消息放到一个中间平台&#xff08;中间件&#xff09; Kafka 分布式流媒体平台 程序发消息&#xff0c;程序接收消息 Producer&#xff1a;Producer即生产者&#xff0c;消息的产生者&#xff0c;是消息的入口。 Brok…