完全背包问题(超级详细地讲解优化过程)

这篇具有很好参考价值的文章主要介绍了完全背包问题(超级详细地讲解优化过程)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、问题描述

完全背包,# DP与贪心题目,算法,动态规划

二、思路分析

完全背包和01背包的区别就在于01背包中,每个物品只能选择一次,而完全背包问题中,每个物品可以选择无限次。如果大家没有看过之前01背包的讲解的话,建议大家先去看看作者之前写的01背包问题,传送门:01背包问题

那么很明显,这道题符合动态规划的三个性质:最优子结构,重叠子问题,无后效性。因此,我们可以利用动态规划的思路去解决这道题。这三个性质的分析和01背包是一样的。

那么想要利用动态规划的思路来解决这道题的话,我们需要做两件事情:

1、构建当前问题和子问题之间的关系:书写状态转移方程
2、设计循环,记录每一个子问题的最优解。

1、状态转移方程

状态转移方程的作用是我们通过数学表达式来达到缩小问题规模。所以,我们需要用子问题来解决当前状态。而在上一节01背包问题中,我们曾经介绍过,对于背包问题中状态转移方程的书写,我们的方式是:活在当下

我们面前要做的选择就是第i个物品,你到底是选还是不选,如果选你能选多少?

很明显,只要在容量允许的范围内,我们可以选很多个,最后我们在各种方案内选出一个最大值。

如下所示:

f ( i , j ) = m a x { f ( i − 1 , j ) f ( i − 1 , j − v [ i ] ∗ 1 ) + w [ i ] ∗ 1 . . . f ( i − 1 , j − v [ i ] ∗ k ) + w [ i ] ∗ k k ∗ v [ i ] ≤ j f(i,j)=max \begin{cases} f(i-1,j)\\ f(i-1,j-v[i]*1)+w[i]*1\\ ...\\ f(i-1,j-v[i]*k)+w[i]*k&k*v[i]\leq j \end{cases} f(i,j)=max f(i1,j)f(i1,jv[i]1)+w[i]1...f(i1,jv[i]k)+w[i]kkv[i]j

2、循环设计

循环的设计其实就是为了有条不紊地逐渐计算出规模不断增大地子问题。完全背包的循环设计和01背包的循环设计是一致的。我们逐一枚举1个物品时,各个容量下的最优解,2个物品时,各个容量下的最优解,直到n个物品下,各个容量下的最优解。

三、代码模板

1、朴素版

#include<iostream>
using namespace std;
const int N=1010;
int f[N][N],v[N],w[N];
int n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        scanf("%d%d",v+i,w+i);
    
    for(int i=1;i<=n;i++)//枚举物品数量
    {
        for(int j=0;j<=m;j++)//枚举容量
        {
            for(int k=0;k*v[i]<=j;k++)//书写状态方程
            {
                f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k);
            }
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

2、优化版

(1)时间优化

我们发现上面的朴素版代码的嵌套了三重循环,这个时间复杂度是非常高的,我们如何降低时间复杂度呢?

同时,我们发现,我们的完全背包问题依旧是只利用 i − 1 i-1 i1行的数据。那么我们能否像01背包一样将二维数组转化成一维数组呢?

我们先解决时间复杂度优化的问题:

我们再看一下我们刚才的状态转移方程:
f ( i , j ) = m a x { f ( i − 1 , j ) f ( i − 1 , j − v [ i ] ∗ 1 ) + w [ i ] ∗ 1 . . . f ( i − 1 , j − v [ i ] ∗ k ) + w [ i ] ∗ k k ∗ v [ i ] ≤ j f(i,j)=max \begin{cases} f(i-1,j)\\ f(i-1,j-v[i]*1)+w[i]*1\\ ...\\ f(i-1,j-v[i]*k)+w[i]*k&k*v[i]\leq j \end{cases} f(i,j)=max f(i1,j)f(i1,jv[i]1)+w[i]1...f(i1,jv[i]k)+w[i]kkv[i]j
我们刚刚的最内层循环其实就是在执行上面这个状态转移方程,最内层的循环在枚举每一种情况,然后去得出一个最大值。

那么我们就聚焦于这个过程:

假设我们当前的背包容量是 j − v [ i ] j-v[i] jv[i],那么此时我们会去枚举该容量下的 k k k种情况。最终得到一个最大值,我们将这个最大值记作: m a x ( j − v [ i ] ) max(j-v[i]) max(jv[i])。此时,我们会将这个最大值记录在数组中。

接着,随着我们的第二层循环的进行,我们到了背包容量为 j j j的情况,此时,我们就会开始枚举 k k k种情况,去求出最大值。但是此时我们思考一下,我们是否还有必要去枚举所有的情况?

k k k = 1 的时候,

我们需要比较的是 : f ( i , j ) f(i,j) f(i,j) f ( i , j − v [ i ] ) + w [ i ] f(i,j-v[i])+w[i] f(i,jv[i])+w[i]

此时假设我们得到的是下面这种结果:

f ( i , j ) > f ( i , j − v [ i ] ) + w [ i ] f(i,j) > f(i,j-v[i])+w[i] f(i,j)>f(i,jv[i])+w[i]

接下来,

k = 2 k=2 k=2的时候,

此时,我们会得到 f ( i , j − v [ i ] ∗ 2 ) + w [ i ] ∗ 2 f(i,j-v[i]*2)+w[i]*2 f(i,jv[i]2)+w[i]2,而这个式子可以改写为: f ( i , ( j − v [ i ] ) − v [ i ] ) + w [ i ] + w [ i ] f\big(i,(j-v[i])-v[i]\big)+w[i] +w[i] f(i,(jv[i])v[i])+w[i]+w[i]

那么这个式子的前半部分: f ( i , ( j − v [ i ] ) − v [ i ] ) + w [ i ] f\big(i,(j-v[i])-v[i]\big)+w[i] f(i,(jv[i])v[i])+w[i] 就可以看作是,我们刚才求状态 f ( i , j − v [ i ] ) f(i,j-v[i]) f(i,jv[i])的最大值时的枚举 k = 1 k=1 k=1的过程。

而我们在此之前我们已经求得了 m a x ( j − v [ i ] ) max(j-v[i]) max(jv[i])。所以此时一定满足:

f ( i , ( j − v [ i ] ) − v [ i ] ) + w [ i ] ≤ m a x ( j − v [ i ] ) f(i,(j-v[i])-v[i])+w[i]\leq max(j-v[i]) f(i,(jv[i])v[i])+w[i]max(jv[i])

两侧再同时加上一个 w [ i ] w[i] w[i]并不影响我们的结果,即:

f ( i , ( j − v [ i ] ) − v [ i ] ) + 2 ∗ w [ i ] ≤ m a x ( j − v [ i ] ) + w [ i ] f(i,(j-v[i])-v[i])+2*w[i]\leq max(j-v[i])+w[i] f(i,(jv[i])v[i])+2w[i]max(jv[i])+w[i]
我们将这个式子一般化:

我们后续过程枚举的任意一个 k k k,都满足下列不等式:

f ( i , j − k ∗ v [ i ] ) + k ∗ w [ i ] = f ( ( i − v [ i ] ) − ( k − 1 ) ∗ v [ i ] ) + ( k − 1 ) ∗ w [ i ] + w [ i ] f(i,j-k*v[i])+k*w[i]=f\big((i-v[i])-(k-1)*v[i]\big)+(k-1)*w[i]+w[i] f(i,jkv[i])+kw[i]=f((iv[i])(k1)v[i])+(k1)w[i]+w[i]
f ( i , ( j − v [ i ] ) − ( k − 1 ) v [ i ] ) + ( k − 1 ) w [ i ] + w [ i ] ≤ m a x ( j − v [ i ] ) + w [ i ] f\big(i,(j-v[i])-(k-1)v[i]\big)+(k-1)w[i]+w[i]\leq max(j-v[i])+w[i] f(i,(jv[i])(k1)v[i])+(k1)w[i]+w[i]max(jv[i])+w[i]

因此,我们与其去枚举每个k,不如直接用之前求出的 m a x ( j − v [ i ] ) max(j-v[i]) max(jv[i])

于是,我们只需要进行一次下列等式的比较即可:
f ( i , j ) = m a x ( f ( i , j ) , m a x ( j − v [ i ] ) + w [ i ] ) f(i,j)=max\big(f(i,j),max(j-v[i])+w[i]\big) f(i,j)=max(f(i,j),max(jv[i])+w[i])

那么用数组表示就是:
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ j − v [ i ] ] + w [ i ] ) f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]) f[i][j]=max(f[i][j],f[i][jv[i]]+w[i])

上述式子表达的意思是,我们选择第 i i i个物品时的最大值,但是我们是不是还有可能不选。
所以我们需要在选的情况下的最大值,和不选的情况下的值做比较。

所以,经过优化后的状态转移方程如下:
m a x = { f [ i ] [ j ] j < v [ i ] m a x ( f [ i ] [ j ] , f [ i ] [ j − v [ i ] ] + w [ i ] ) j ≥ v [ i ] max= \begin{cases} f[i][j]&j<v[i] \\max\big(f[i][j],f[i][j-v[i]]+w[i]\big)&j\geq v[i] \end{cases} max={f[i][j]max(f[i][j],f[i][jv[i]]+w[i])j<v[i]jv[i]

上述的式子就可以直接代替第三层循环。

时间优化版本:

#include<iostream>
using namespace std;
const int N=1100;
int f[N][N],v[N],w[N];
int n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)scanf("%d%d",v+i,w+i);
    
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            f[i][j]=f[i-1][j];
            if(j>=v[i])
            f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

(2)空间优化

时间+空间优化的版本:

我们在01背包的文章中曾经介绍说,逆序进行第二层循环是为了避免重复选择。而我们的完全背包是允许进行重复选择的。如果大家没看过之前的01背包问题的文章的话,作者建议先去看一看01背包的优化,01背包传送门

因此,我们正序遍历第二层循环的情况下就可以进行空间优化,代码如下:文章来源地址https://www.toymoban.com/news/detail-633358.html

#include<iostream>
using namespace std;
const int N =1e3+10;
int f[N],v[N],w[N];
int n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",v+i,w+i);
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            if(j>=v[i])
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

到了这里,关于完全背包问题(超级详细地讲解优化过程)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

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

    目录 思路 朴素代码 优化 公式推导上  二维代码  一维代码 公式理解上   在开始看完全背包问题之前,可能需要先了解01背包及其解决办法。 指路👇 【第二十五课】动态规划:01背包问题(acwing-2 / 思路 / 含一维数组优化 / c++代码) 这个问题和01背包的区别就是 每件物品可以

    2024年03月19日
    浏览(64)
  • 动态规划详细讲解c++|经典例题讲解认识动态规划|0-1背包问题详解

    uu们,你们好!这次的分享是动态规划,其中介绍了动态规划的相关概念和做题模板(三要素),同时为了uu们对动态规划方法有更加形象的认识,特地找了两个经典问题,和大家一起分析。并且呢,为了大家检验自己的学习成果,我专门在常用的oj上为uu们找到了相关题目的

    2024年04月11日
    浏览(60)
  • 从01背包开始动态规划:暴力解法 + dp + 滚动数组 + dp优化

        01背包问题是动态规划中最经典的问题之一,本篇将通过给出其四种解法,使读者更深刻理解动态规划。   有N件物品和一个容量是 V 的背包,每个物品有各自的体积和价值,且每个物品只能放一次(这也是01背包名字的由来),如何让背包里装入的物品具有最大的价值总

    2024年04月17日
    浏览(54)
  • 动态规划DP之背包问题3---多重背包问题

    目录 DP分析: 优化:  二进制优化 例题:         01背包是每个物品只有一个,完全背包问题是每个物品有无限个。         那么多重背包问题就是 每个物品有有限个 。 有 N 种物品和一个容量是 V 的背包。 第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。 求解

    2024年03月20日
    浏览(49)
  • 背包问题——01背包|完全背包

    目录 前言背包问题的历史  01背包  1、题目 2、暴力解01背包  Ⅰ、代码 3、动态规划解01背包 Ⅰ、二维dp数组解01背包 1)dp数组的含义  2)递推公式  3)dp数组的初始化  4)遍历顺序的讨论  5、代码  Ⅱ、一维数组解01背包  1)一维数组|滚动数组  2)一维数组的含义及递

    2024年02月02日
    浏览(48)
  • 动态规划-背包问题-完全背包

    对比01背包,完全背包中的每件物品有无数件。 也就是说,每件物品可以拿0,1,…,k,…件。 dp[i][j]表示前i种物品,体积为j时的最大价值 对于第i件物品: 不拿:dp[i][j]⇐dp[i-1][j] 拿一件:dp[i][j]⇐dp[i-1][j-w[i]]+v[i] 拿两件:dp[i][j]⇐dp[i-1][j-2w[i]]+2v[i] … 拿k件:dp[i]][j]⇐dp[i

    2024年04月08日
    浏览(49)
  • 【DP】学习之背包问题

    2. 01背包问题 - AcWing题库 记忆化搜索  这里补个图,DFS是自顶向下推的 dp的递推是从下往上 用一维数组代替二维数组  进一步优化 这里m从m走到0,的原因是只让每个物品最多拿一次。 如果正序枚举体积,就会让物品被拿多次,从而违反规则, 但完全背包不用考虑这个问题,

    2024年02月03日
    浏览(58)
  • 动态规划之背包问题——完全背包

    算法相关数据结构总结: 序号 数据结构 文章 1 动态规划 动态规划之背包问题——01背包 动态规划之背包问题——完全背包 动态规划之打家劫舍系列问题 动态规划之股票买卖系列问题 动态规划之子序列问题 算法(Java)——动态规划 2 数组 算法分析之数组问题 3 链表 算法

    2024年02月03日
    浏览(64)
  • 完全背包&多重背包问题(动态规划)

    完全背包问题: 每个物品使用次数没有限制,与0-1背包的不同之处在于 遍历背包的顺序 是正序。 多重背包问题: 与完全背包的区别在于,每一种物品是有个数限制的,不能无限选择。这篇博客讲解的非常详细,可以参考学习: 多重背包问题---超详细讲解+优化(不懂你揍我

    2024年04月10日
    浏览(48)
  • C++ DP算法,动态规划——背包问题(背包九讲)

    有N件物品和一个容量为 V V V 的背包。放入第i件物品耗费的空间是 C i C_i C i ​ ,得到的价值是 W i W_i W i ​ 。 求解将哪些物品装入背包可使价值总和最大。 这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。 用子问题定义状态:即 F [ i , v ] F[i, v] F

    2024年02月16日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包