01背包、完全背包问题几种变式总结,以及多重背包、组合背包模板

news2024/11/15 11:13:14

目录

1.求有多少种方法能恰好装满背包

1.1装满背包的方法——按排列计算还是按组合计算?

2.最值问题——最少需要几枚硬币,货物的最大价值

2.1最少需要几枚硬币

2.1.1 memset用法注意

3.二维01背包问题

4.多重背包问题

4.1优化前

4.2二进制优化


1.求有多少种方法能恰好装满背包

这种情况下我们一般令dp[ j ]的含义为:装满容量为 j 的背包的方法有dp[ j ]种。

因此这种情况的公式都是:dp[ j ] += dp[ j-nums[ i ] ]

1.1装满背包的方法——按排列计算还是按组合计算?

组合与顺序无关,只在乎元素,比如[1,2]和[2,1]是同一个组合;排列与顺序有关,所以[1,2]和[2,1]不是同一个组合。

在解决背包问题时,我们有内外两层循环:

在计算 组合 方法数时,外层循环依次是每一个元素,内层循环是遍历一整个背包;

在计算 排列 方法数时,外层循环依次是每个容量下的背包,内层循环是遍历所有元素。

因为如果外层循环依次遍历每一个元素,那么背包里记录的数据,一定是先记录的nums[ 0 ],再记录的nums[ 1 ],不会出现反过来的情况,也就是只存在[ 1,2 ]不存在[ 2, 1 ],自然而然得到的结果是按组合总数得到的。

 而计算排列的原理个人觉得比较难理解,但是我们可以通过一点数学的方法来推导整个过程。这里引用LeetCode.377题目的一条评论:

组合例题:LeetCode.518.零钱兑换II

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        int dp[5005]={0};
        //dp[j]表示,如果确定有一个元素coins[i],那么有dp[j]种组合数凑成j
        dp[0]=1;
        for(int i=0;i<coins.size();++i)
        {
            for(int j=coins[i];j<=amount;++j)
            {
                dp[j] += dp[j-coins[i]];
            }
        }
        return dp[amount];
    }
};

排列例题:LeetCode.377.组合总和IV

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        int dp[1005]={0};
        int MAX = 0x7fffffff;
        dp[0] = 1;
        for(int i=1;i<=target;++i)
        {
            for(int j=0;j<nums.size();++j)
            {
                if(i-nums[j]>=0&&dp[i]<MAX-dp[i-nums[j]])
//这里的两个判断条件,前一个必写,后一个视题目数据量大小而定
                dp[i] += dp[i-nums[j]];
            }
        }
        return dp[target];
    }
};

2.最值问题——最少需要几枚硬币,货物的最大价值

2.1最少需要几枚硬币

LeetCode.322.零钱兑换

 先考虑外层循环依次是背包的容量(需要凑成的总面值),内层循环依次遍历所有面值的硬币的方法

首先将原问题分解成子问题

例如需要凑成 7 块钱,有[1,2,5]三种面值的硬币,求最少的硬币数dp[7],它的子问题是:

(1)需要凑成 7-1=6 块钱,有[1,2,5]三种面值的硬币,求最少的硬币数dp[6]+1;

(2)需要凑成 7-2=5 块钱,有[1,2,5]三种面值的硬币,求最少的硬币数dp[5]+1;

(3)需要凑成 7-5=2 块钱,有[1,2,5]三种面值的硬币,求最少的硬币数dp[2]+1;

而要求dp[7]最小,因此dp[7]要取min{ dp[6]+1,dp[5]+1,dp[2]+1 },得到状态方程如下:

dp[ i ] = min( dp[ i ],dp【i - coins[ j ]】 +1 )

其中 i 表示当前背包的容量

然后就像1.1中例题377组合总数一样,一直递推下去,这样只要直到dp[0](视具体题目进行初始化),就能知道dp[7]的值。由于计算dp[7]时,dp[6],dp[5],dp[2]必须已知,所以从dp[1]开始计算。(视具体初始化情况而定,已经初始化了的元素就没必要再计算一遍了)

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int dp[10005];
        //dp[i]表示凑成 i 块钱最少需要几枚硬币
        memset(dp,0x7f,sizeof(dp));
        dp[0] = 0;
        int len = coins.size();
        for(int i=1;i<=amount;++i)
        {
            for(int j=0;j<len;++j)
            {
                if(i-coins[j]>=0)
                dp[i]=min(dp[i],dp[i-coins[j]]+1);
            }
        }
        return dp[amount]==0x7f7f7f7f?-1:dp[amount];
    }
};

 从理解的角度,上面的代码比较容易理解,在理解了之后我们可以更换顺序来做,这样会更快

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int dp[10001];
        memset(dp,0x7f,sizeof(dp));
        dp[0] = 0;
        int len=coins.size();
        for(int j=0;j<len;++j){
            for(int i=coins[j];i<=amount;++i){
//快在这里的 i 可以从coins[j]开始
                dp[i] = min(dp[i],dp[i-coins[j]]+1);
            }
        }
        return dp[amount]==0x7f7f7f7f?-1:dp[amount];
    }
};

2.1.1 memset用法注意

首先是格式,对数组 a 初始化,格式为:memset( a,x,sizeof(a) ),其中 x 是想要初始化为的数。一般我们取最大值、1、0、-1等。

取“最大值”的方法:memset( a,0x7f,sizeof(a) ),而且这样初始化之后,一个int能达到的值并不是MAX_INT,因为此时一个int为:0x7f7f7f7f,而非0x7fffffff。

原因是:memset是对每1个字节(每8个二进制位)进行初始化。

3.二维01背包问题

LeetCode.474.一和零

 也就是同时存在两个01背包,一个背包装0,容量是m;另一个背包装1,容量是n。

在之前做01背包问题一维优化的时候,我们提到内层循环中,背包应从最大容量开始逆序遍历,将这个问题转为二维也是一样的(因为如果从0开始遍历,则会覆盖掉前面存入的数据,最后结果就错了),我们定义dp[ i ][ j ]意为m=i,n=j时背包能装下的最大容量。

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int dp[105][105]={0};
        //dp[i][j]意为有 i 个0,j 个1的情况下,最大子集中元素个数
        
        int len = strs.size();
        for(int k=0;k<len;++k)
        {
            int cnt0=0,cnt1=0;
            for(char c : strs[k])
            {
                if(c == '0') ++cnt0; 
                else ++cnt1;
            }
            for(int i=m;i>=cnt0;--i)
            {
                for(int j=n;j>=cnt1;--j)
                {
                    dp[i][j] = max(dp[i-cnt0][j-cnt1]+1,dp[i][j]);
                }
            }
        }
        return dp[m][n];
    }
};

这里需要注意将计算0、1的个数的循环放在dp循环里一起算,这样就不用专门开两个数组cnt1[ ]和cnt0[ ]用来保存每个字符串中0、1的数量,而且也免去了多余的两层for循环计算。

4.多重背包问题

4.1优化前

由于数据范围比较小,O(N^3)=O(100^3) 也可以不超时,可以先从暴力解法入手,作为过渡。

需要注意的点:

(1)由于每种物品是有规定其能取的上限的,因此需要在之前01背包(而非完全背包,具体原因在第二点)的基础上,再引入一个循环用于遍历某个物品 取0个、取1个……取s个的所有情况。

(2)一维优化后的01背包中,内层循环需要逆序,而一维优化的完全背包中,内层循环无需逆序。在多重背包问题中,内层循环依旧需要逆序,这也是为什么说它是建立在01背包的基础上的。 可以理解为,01背包和多重背包都是某种物品取有限个,而完全背包允许某个物品取无穷多个,只要背包装得下。

#include<iostream>
using namespace std;

int n,m;
int dp[110]={0};

int main()
{
    cin>>n>>m;
    for(int i=0;i<n;++i)
    {
        int v,w,s;
        cin>>v>>w>>s;
        for(int j=m;j>=1;--j)
            for(int k=0;k<=s&&v*k<=j;++k)
                dp[j] = max(dp[j],dp[j-v*k]+w*k);
    }
    cout<<dp[m];
    return 0;
}

4.2二进制优化

这道题是多重背包 I 在数据上的加强。数据量决定了用优化前的做法极大概率会超时。

前面说了,多重背包问题实际上是建立在01背包问题的基础上的,那么我们可以考虑将多重背包再化为01背包(比如一种物品有10件,那就把它当成十件种类不同,但是重量和价值都一样的物品,这样每件物品只能取一次,也就是01背包问题),这样复杂度就能向01背包靠拢了。 

可惜如果只是简单的转化,也是一个O(N^3)的过程。

由此引出了二进制优化的方法。能将原来每件商品的件数 Si 减少到logSi。

#include<iostream>
using namespace std;

int n,m,cnt=0;
int dp[2200]={0};
int w[22000];
int v[22000];

int main()
{
    cin>>n>>m;
    //以下先进行二进制优化过程
    for(int i=0;i<n;++i)
    {
        int a,b,s;
        cin>>a>>b>>s;
//每次进行while循环之前记得把k初始化为 1
        int k=1;
//着重注意下面这个while循环的写法
        while(k<=s)
        {
            cnt++;
            w[cnt]=k*a;
            v[cnt]=k*b;
            s-=k;
            k*=2;
        }
        if(s>0)
        {
            cnt++;
            w[cnt]=a*s;
            v[cnt]=b*s;
        }
    }
//cnt实际上记录的是二进制优化过后,01背包中商品的总件数
    n=cnt;
    //以下进行01背包过程
    for(int i=1;i<=n;++i)
    {
        for(int j=m;j>=w[i];--j)
        {
            dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
    cout<<dp[m];
    return 0;
}

5.组合背包问题

组合背包没有很好的解题方法,只能用三重循环来做,但是其中也有一些注意事项。

 

#include<iostream>
using namespace std;

int N,V;
int dp[110],val[110],wei[110];

int main()
{
    cin>>N>>V;
    for(int i=0;i<N;++i)
    {
        int s;
        cin>>s;
        for(int j=0;j<s;++j) cin>>wei[j]>>val[j];
        for(int j=V;j>0;--j)
            for(int k=0;k<s;++k)
            	if(j>=wei[k])
                	dp[j]=max(dp[j],dp[j-wei[k]]+val[k]);
    }
    cout<<dp[V];
    return 0;
}

第三重循环中的写法需要特别注意。

一开始我写的是这样:

 因为上面提到的多重背包,优化前的写法是:

这样写能提前break掉,节省一点时间。但这是因为多重背包中,物品件数是依次递增的,因此一旦 v*k>j ,之后k会一直增大,v*k也一定一直大于 j,因此可以直接break掉,能做对且节省时间。

但是组合背包中,输入的物品并不是按重量依次递增的,也就是说,如果背包容量为5,第k-2件物品的重量是6,但是第k-1件物品的重量可能是1,这样一来,如果遇到重量为6的物品就直接break 的话,就无法遍历到第k-1件物品;因此这里不能提前break,而是每件物品都要去判断。 

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

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

相关文章

C++STL库中的list

文章目录 list的介绍及使用 list的常用接口 list的模拟实现 list与vector的对比 一、list的介绍及使用 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。 2. list的底层是双向带头循环链表结构&#xff0c;双向带头循…

数据库对象

二十、数据库对象-视图 二十一、数据库对象-索引 age字段没有索引&#xff0c;查找需要扫描全表&#xff1a; name字段做了唯一索引&#xff0c;查找一次&#xff1a; 二十二、数据库对象-事务 事务的隔离级别和问题&#xff1a;

(链表) 剑指 Offer 52. 两个链表的第一个公共节点 ——【Leetcode每日一题】

❓剑指 Offer 52. 两个链表的第一个公共节点 难度&#xff1a;简单 输入两个链表&#xff0c;找出它们的第一个公共节点。 如下面的两个链表&#xff1a; 在节点 c1 开始相交。 示例 1&#xff1a; 输入&#xff1a;intersectVal 8, listA [4,1,8,4,5], listB [5,0,1,8…

三星GalaxyWatch放弃iOS:无法给用户一致的体验,还不如“丢掉”

昨晚&#xff0c;三星发布了全新的Galaxy Watch 6系列智能手表。然而&#xff0c;对于苹果手机用户来说&#xff0c;这个消息可能并不那么重要。因为从2021年开始&#xff0c;三星决定转向Wear OS系统&#xff0c;并计划在Galaxy Watch 4及以后的新款智能手表上采用该系统&…

Python基础语法第八章之使用库

目录 一、使用库 二、标准库 2.1认识标准库 2.2使用 import 导入模块 2.3第三方库 2.3.1认识第三方库 2.3.2使用 pip 一、使用库 库 就是是别人已经写好了的代码, 可以让我们直接拿来用. 按照库的来源, 可以大致分成两大类 标准库: Python 自带的库. 只要安装了 Pytho…

JavaEE——SpringMVC中的常用注解

目录 1、RestController &#xff08;1&#xff09;、Controller &#xff08;2&#xff09;、ResponseBody 2、RequestMappping &#xff08;1&#xff09;、定义 &#xff08;2&#xff09;、使用 【1】、修饰方法 【2】、修饰类 【3】、指定方法类型 【4】、简化版…

朝花夕拾思维导图怎么画?看看这种绘制方法

朝花夕拾思维导图怎么画&#xff1f;绘制思维导图的好处有很多&#xff0c;首先它可以帮助人们更好地组织和管理知识&#xff0c;提高工作效率和学习效果。其次&#xff0c;绘制思维导图可以帮助人们更好地记忆知识点和理解知识点。总之&#xff0c;绘制思维导图可以帮助人们更…

字符串函数介绍应用

字符串 1.前言 C语言中对字符和字符串的处理很是频繁&#xff0c;但是C语言本身是没有字符串类型的&#xff0c;字符串通常放在 常量字符串中或者字符数组中。 字符串常量适合于那些对他不做修改的函数。 2.库函数及其模拟实现 2.1 strlen函数 size_t strlen ( const char *…

机器学习深度学习——多层感知机的简洁实现

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——多层感知机的从零开始实现 &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你…

东南大学轴承故障诊断(Python代码,CNN模型,适合复合故障诊断研究)

运行代码要求&#xff1a; 代码运行环境要求&#xff1a;Keras版本>2.4.0&#xff0c;python版本>3.6.0 本次实验主要是在两种不同工况数据下&#xff0c;进行带有复合故障的诊断实验&#xff0c;没有复合故障的诊断实验。 实验结果证明&#xff0c;针对具有复合故障的…

Linux系统MySQL数据库的备份及应用

本节主要学习了MySQL数据库的备份&#xff1a;概念&#xff0c;数据备份的重要性&#xff0c;造成数据丢失的原因&#xff0c;备份的类型&#xff0c;常见的备份方法&#xff0c;实例与应用等。 目录 一、概述 二、数据备份的重要性 三、造成数据丢失的原因 四、备份类型 …

AMEYA360:ROHM罗姆授权代理有哪些?

罗姆(ROHM)株式会社是全球知名的半导体厂商之一&#xff0c;总部所在地设在日本京都市&#xff0c;1958年作为小电子零部件生产商在京都起家的罗姆&#xff0c;于1967年和1969年逐步进入了晶体管、二极管领域和IC等半导体领域。2年后的1971年&#xff0c;罗姆作为第一家进入美国…

K8S故障排查

故障现象&#xff1a;部署pod时&#xff0c;报错没发调度到节点。 排查步骤&#xff1a; 1、查看集群的状态 [rootk8s-master1 nginx]#kubectl get nodes2、查看k8s组件的状态-kubelet&#xff0c;kube-apiservice 3、查看docker的Cgroup driver和k8s的Cgroup driver类型&…

list源码分析,基于c++ 和vs2019,cpp20标准

list源码分析,基于c 和vs2019&#xff0c;cpp20标准。结构确实如图&#xff0c;双向环形链表。

Qt C++实现Excel表格的公式计算

用Qt的QTableViewQStandardItemModelQStyledItemDelegate实现类似Excel表格的界面&#xff0c;在parser 模块中提供解析表格单元格输入的公式。单元格编辑结束后按回车进行计算和更新显示。 效果如下&#xff1a; 支持的公式计算可以深度嵌套&#xff0c;目前parser模块中仅提…

【C语言day08】

int n5; int a[n][n2] 数组定义下角标不能为变量 注&#xff1a;C99标准中支持了使用变量本题考查的是二维数组的元素访问&#xff0c;A选项是 正确的&#xff0c;X[i]就是第i行的数组名&#xff0c;数组名表示首元素的地址&#xff0c;X[i]表示第i行的第一个元素的地址&#…

【开源项目】智慧高铁站~经典开源项目数字孪生智慧高铁站——开源工程及源码

广州南站工程和源码免费赠送&#xff0c;人人都可探索其魅力&#xff01; 项目介绍 广州南站&#xff0c;中国最大的综合交通枢纽之一&#xff0c;处于广州市珠江新城中轴线上&#xff0c;是广州南沙新区的门户之一。利用数字孪生技术&#xff0c;通过3Dmaxs技术实现数据和场景…

MySQL基础扎实——MySQL中各种数据类型之间的区别

在MySQL中&#xff0c;有各种不同的数据类型可供选择来存储不同类型的数据。下面是一些常见的数据类型以及它们之间的区别&#xff1a; 整数类型&#xff1a; TINYINT&#xff1a;1字节&#xff0c;范围为-128到127或0到255&#xff08;无符号&#xff09;。SMALLINT&#xff1…

Hadoop的伪分布式安装方法

实验环境&#xff1a; 操作系统&#xff1a;Linux (Ubuntu 20.04.5) Hadoop版本&#xff1a;3.3.2 JDK版本&#xff1a;1.8.0_162 &#xff08;1&#xff09;创建 hadoop 用户&#xff08;使用 /bin/bash 作为 Shell&#xff09;、设置密码&#xff08;建议简单&#xff09…

YOLO算法改进指南【中阶改进篇】:9.添加S2-MLPv2注意力机制

一、理论知识 S2MLPv2 依是百度提出的用于视觉的空间位移 MLP 架构,其作者以及顺序与 S2MLP 一模一样,其论文为 S2-MLPv2: Improved Spatial-Shift MLP Architecture for Vision。S2MLPv2 的修改点主要在于三处:金字塔结构(参考 ViP)、分三类情况进行考虑(参考 ViP)、使…