【第二十五课】动态规划:多重背包和分组背包(acwing-4,5,9 /思路 / c++代码)

news/2024/6/16 9:57:50 标签: 动态规划, 算法, c++

目录

acwing-4

朴素代码 

acwing-5

二进制优化思路

代码

acwing-9

思路 

代码


在看多重背包和分组背包之前,要先有01背包和完全背包基础哦。指路👇

【第二十五课】动态规划:01背包问题(acwing-2 / 思路 / 含一维数组优化 / c++代码)

 【第二十五课】动态规划:完全背包问题(acwing-3 / 公式推导 / 思路理解 / 优化 / c++代码)

acwing-4

朴素做法(三层循环)比较好写,在之前的基础上,就不再多说了。只是要注意数据范围,稍微大点这个就用不了了。

朴素代码 

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int dp[N][N];
int v[N],w[N],s[N];
int main()
{
    cin>>n>>m;

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

acwing-5

这里数据范围很大,用三层循环会超时 

二进制优化思路

相比完全背包来说,这里每件物品有多少件是题目给的,我们并不能确定。因此不能按照之前那样推导。

因此我们需要考虑其他的优化方法。

这里主要就是数据范围的问题。

我们要创建的v[i]w[i]数组分别表示能够存储所有可能的物品的价值和体积,N表示物品种数,V表示背包容量,每种物品的体积价值和数量最大都是2000,在这样的情况下,我们应该可以确定,创建的v[i]w[i]数组应该大小为1000*2000*2000,含义是最多1000种物品*每种物品最多有2000件*每件物品的最大体积/价值都是2000=4e9。这样开辟的数组空间简直是太大了。因此我们提出了二进制化的思想。

同样如果我们不对s进行处理,那么代码时间复杂度将会达到N*V*S=4e9

这里的二进制化是指对同一种有si件的物品进行二进制化

我们说n件不同的物品一共有2的n次方种选择方案,因为每种物品都有选与不选两种选择。但是对于si件相同的物品有多少种选择方案呢?其实只有(像完全背包那样的)0,1,2,3,...,si,种方案。如果我们要把这0-si全举出来,那情况就太多了。因此我们想到通过将相同数量的物品按照二进制的方式进行分组,可以减少要考虑的情况

也就是我们将这si件相同的物品按照二的次方来进行分组,比如7:分成1 2 4,这样只用1 2 4 就可以表示出0-7这八种可能,10:分成1 2 4 3 ,因为我们只要11种选择就可以了,最后一个数如果是2^3=8的话,1 2 4 8 所能组成的就不止11种了,因此我们最后一个数的选择一定是<8的。

通过这种分组方式,我们可以用每组中物品数量的组合来表示总数量,而不需要列举每种可能的组合。比如,对于si件物品,我们只需要考虑从每组中选取0到最大数量的可能性,而不需要列举每种具体的组合

如何理解这里0-7的八种情况可以被1 2 4这三个数凑出来?

我们分组后1 2 4 是把原来7个相同的物品,分成了3个不同的物品,这里所说的“不同的物品”,其实和题目所定义的不同种物品的含义是一样的,那么那些不同种物品怎么构成方案,这些重新组合而形成的不同种物品就怎么构成不同的方案。我们会将选1 2构成3,选2 4 构成6,其实不是2被用了很多次,只是在处理不同的组合方案。(我之前对这里有误解)

因此我们说二进制化之后,我们是把多重背包问题转换成了01背包问题


至此,思路我们应该讲明白了。

下面这个老师讲得太清晰了,非常推荐看一下~ 

 【多重背包的二进制优化-CSP/NOIP/蓝桥杯/ACM/青少年C++编程试听课】

代码

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10, M = 2010;
int n, m;
int v[N], w[N];
int dp[M];
int main()
{
    cin >> n >> m;
    int count = 0;//记录当前物品种类的数量
    for (int i = 1; i <= n; i++)
    {
        int a, b, s;
        cin >> a >> b >> s;//因为可能要进行二进制化,所以不能直接复制到v w数组中
        int k = 1;//表示每种物品在二进制拆分时的数量,初始值为1,表示从拆分后的最小数量开始,也就是2的0次方
        while (k <= s)
        {
            count++;
            v[count] = a * k;
            w[count] = b * k;
            s -= k;
            k *= 2;
        }
        if (s > 0)//处理剩余的物品数量,如果剩余数量不为0,则将剩余数量作为一个单独的物品添加到数组中。
        {
            count++;
            v[count] = a * s;
            w[count] = b * s;
        }
    }
    n = count;
    //直接写01背包的一维优化
    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j >= v[i]; j--)
        {
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
        }
    }
    cout << dp[m];
    return 0;
}

这样我们的时间复杂度就由N*V*S优化为N*logs*V

acwing-9

思路 

在分组背包问题中,对于每组物品,我们的选择是放入该组中的一个物品或不放入该组任何物品。这与01背包问题的情况类似,只是分组背包问题中的每组物品可以看作是01背包问题的一个子问题。

也就是说我们这一层的数据需要通过上一层才能得到。因此在遍历背包容量时,也应该逆序遍历。

从思路上来说:在分组背包问题中,每组物品内的物品之间是相互影响的,因此,为了确保状态的正确性,在考虑放入当前组的物品时,需要确保之前组的物品已经正确地放入背包,所以逆序遍历背包容量可以保证每次更新都是基于之前状态的正确值。

代码

#include<iostream>
#include<algorithm>
using namespace std;
const int N=110;
int n,m;
int v[N][N],w[N][N],s[N];
int dp[N];
int main()
{
    cin>>n>>m;//n表示组数

    for(int i=1;i<=n;i++)
    {
        cin>>s[i];//表示这一组的物品数量
        for(int j=0;j<s[i];j++)
        {
            cin>>v[i][j]>>w[i][j];
        }
    }

    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=0;j--)
        {
            for(int k=0;k<s[i];k++)//涉及到不选这组物品/选这组物品的哪一个
            {
                if(v[i][k]<=j)
                {
                    dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]);
                }
            }
        }
    }
    cout<<dp[m];
    return 0;
}

在 01背包问题中,j >= v[i] 中的 i 表示的是当前物品的索引,而不是组的索引对于每个物品 i,我们都知道它的体积 v[i],所以可以直接在循环中检查背包容量是否大于等于当前物品的体积

在分组背包问题中,i 表示的是组的索引,每个组内的物品数量和体积是不确定的所以我们需要通过内部循环遍历每个组内的物品,并检查每个物品的体积是否小于等于当前的背包容量。因此不能像 01背包问题中那样直接使用 j >= v[i]。


那背包问题就写到这里啦。

有问题欢迎指出,一起加油!!!


http://www.niftyadmin.cn/n/5404171.html

相关文章

u-boot 基础学习:板级配置 Kconfig 的包含

前言 u-boot 与 Linux 内核在嵌入式Linux开发中占有重要的地位&#xff0c;掌握 u-boot 的基础开发&#xff0c;可以大大提升开发能力&#xff0c;并提高开发的效率。 u-boot 下 如何配置 板级的Kconfig 呢&#xff1f;u-boot 下板级的 Kconfig 是怎么包含到 主目录下 Kconfig…

HarmonyOS Next 实现登录注册页面(ARKTS) 并使用Springboot作为后端提供接口

1. HarmonyOS next ArkTS ArkTS围绕应用开发在 TypeScript &#xff08;简称TS&#xff09;生态基础上做了进一步扩展&#xff0c;继承了TS的所有特性&#xff0c;是TS的超集 ArkTS在TS的基础上扩展了struct和很多的装饰器以达到描述UI和状态管理的目的 以下代码是一个基于…

【STK】手把手教你利用STK进行仿真-STK软件简介05 STK部分第三方分析模块介绍

1.导弹建模工具MMT 导弹建模工具MMT(Missile Modeling Tools)是STK在导弹分析领域的扩展分析应用,它是由四个独立的应用程序组成的相互支持与关联的系统,由第三方研究机构开发,能够与STK基本航天分析环境进行联合仿真分析。MMT主要用于导弹总体设计(包括弹道导弹、巡航导弹…

node.js实现结合 RSA 和 AES 加密算法的消息交换加密传输深入理解 node 中的 crypto 加密模块

使用消息摘要算法对消息体计算和验证摘要&#xff0c;可以防止消息传输过程中被篡改为非法消息值&#xff1b;使用加密算法加密消息体&#xff0c;可以防止消息传输过程中被拦截并读取。二者结合则可以实现较强的安全性消息交换。 1 保证消息交换正确性 消息传输过程中可能被…

消防主机报故障时发出故障及原因及解决办法!

本文以青鸟消防JBF-11SF为例。 其他型号或品牌的消防主机也可参考。 开机前&#xff0c;必须先测量系统接线的绝缘电阻&#xff0c;确保各绝缘电阻满足以下要求&#xff1a; 1&#xff09;空载时各电路信号线之间的绝缘值应大于5K欧姆。 2&#xff09;正常天气条件下&#x…

【蓝桥杯】错误票据

今天是2024年3月1号&#xff0c;蓝桥杯比赛还有一个月的时间&#xff0c;虽说自己不指望拿奖吧&#xff0c;但是还是有些莫i名的焦虑&#xff0c;这道题目都做不出来&#xff0c;感觉自己真的有点菜啊&#xff01;但是还好啦&#xff0c;我觉得是因为我没有题感&#xff0c;慢慢…

系统架构设计文档模版

XX 系统架构设计方案 修订记录 日期 版本号 修订说明 修订人 审核人 1、概述... 5 1.1&#xff0e;业务背景... 5 1.2&#xff0e;系统总体描述... 5 1.3&#xff0e;系统边界图... 5 1.4&#xff0e;名词和缩略语... 5 1.…

IntelliJ IDEA 常用的插件

IntelliJ IDEA有很多常用的插件&#xff0c;这些插件可以扩展IDE的功能&#xff0c;提高开发效率。以下是一些常用的插件&#xff1a; Maven Helper&#xff1a;这是一款分析Maven依赖冲突的插件。在没有此插件时&#xff0c;查看Maven的依赖树和检查依赖包冲突可能需要输入命…