背包问题基础模型全解

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

01背包

Acwing 2. 01背包问题

状态表示:二维

集合:只从前 \(i\) 个物品里面选择总体积 \(\leq j\) 选法的集合

属性:选法价值的最大值

状态计算分为 放 \(i\) 和 不放 \(i\) (要不要把当前物品放进背包):

  • 不放 \(i\) 意味着在前 \(i-1\) 个物品里面选,且总体积不超过 \(j\)
  • \(i\) 的话先来看看里面应该都是些什么东西

背包问题基础模型全解

如图所示,\(f[i][j]\) 表示的是 \(0\)\(i\) 里面所有选法的权值和的最大值,我们可以将 \(f[i][j]\) 拆成两部分来看待,即 \(f[i-1][j-v[i]]\)\(i\)

那么这两段的权值和为 \(f[i-1][j-v[i]]+w[i]\) ,由于求最大值,所以我们要比较一下那种选择可以使权值最大,于是我们就得到了状态转移方程

\[f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]) \]

由于下标不能是负数,所以上述转移方程要求 \(j\geq w[i]\) 。当 \(j<w[i]\) 时,\(f[i][j]=f[i-1][j]\)

综上所述,可以得出:

\[f[i][j]=\left\{ \begin{align} &f[i-1][j],&j<w[i] \\ &max(f[i-1][j],f[i-1][j-v[i]]+w[i]),&j\geq w[i] \end{align} \right. \]
#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++) cin>>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-1][j],f[i-1][j-v[i]]+w[i]);
        }
    }
    cout<<f[n][m];
    return 0;
}

注意题目给的数据,我们的数组的第二维是跟着容量的范围而不是物品数的范围

优化版本:
我们可以发现在 \(f[i][j]\) 的时候只用到了 \(f[i-1][j]\) ,这样 \(f[i-2][j]\)\(f[i-3][j]\) …… 就被浪费掉了,而且,我们二维的状态只用到了\(j\)\(j-w_i\) 都是 \(\leq j\) 不会在两侧,于是我们就可以用滚动数组来优化这个问题

首先,我们直接删除掉 \(i\) 这个状态,那么原方程就会变成:

\[f[j]=\left\{ \begin{align} &f[j],&j<w[i] \\ &max(f[j],f[j-v[i]]+w[i]),&j\geq w[i] \end{align} \right. \]

由于 \(f[j]=f[j]\) 是恒等式,直接删去,由于 \(max(f[j],f[j-v[i]]+w[i])\) 要求 \(j\geq w[i]\) ,所以 \(j\) 直接从 \(w[i]\) 开始枚举

好,接下来再看变形后的式子与原来的方程是否是等价的,答案是否定的

由于我们的 \(j\) 是从小到大枚举的,所以 \(j-v[i]\) 会在 \(j\) 之前被算,相当于二维的 \(f[i][j-v[i]]\) 与原来的方程不符,那么如何解决这个问题呢? 我们可以让 \(j\) 从大到小枚举,这样就保证了当我们计算 \(f[j]\) 的时候 \(f[j-v[i]]\) 因为比 \(j\) 小,所以还没有被更新,用的就是 \(i-1\) 层的 \(f[j-v[i]]\) ,问题解决

01背包如何保证拿一次? 那么当前背包的体积就要去找前面的,而前面的都肯定是没有拿过这个物品的,所以可以保证只拿一次。

当然我们可以边输入边计算,又可以省下数组的空间

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

完全背包

Acwing 3 完全背包问题

状态表示:二维

集合:所有只考虑前 \(i\) 个物品,且总体积 \(\leq j\) 选法的集合

属性:选法价值的最大值

状态计算以第 \(i\) 个物品选几个为划分,注意体积不能超过 \(j\) ,即 \(k×v[i]\leq j\)

注意当 \(k=0\) 时就相当于不选,所以需要从 \(0\) 开始枚举,这里还需要对 \(f[i][j]\) 打擂台,有别于01背包

我们可以得出状态转移方程

\[f[i][j]=max(f[i][j],f[i-1][j-k×v[i]]+w[i]) \]
#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++) cin>>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-v[i]*k]+w[i]*k);
    cout<<f[n][m];
    return 0;
}

那么这样的时间复杂度大概是 \(O(m^2n)\) 的,显然不行

我们来想想如何优化

首先,将原方程展开得:

\[f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-2×v[i]]+2×w[i],...) \]

然后再与 \(f[i][j-v[i]]\) 做一下对比:

\[f[i][j-v[i]]=max(f[i-1][j-v[i]],f[i-1][j-2×v[i]]+w[i],...) \]

那么我们可以发现,\(f[i][j]\) 仅仅比 \(f[i][j-v[i]]\) 多了一个 \(f[i-1][j]\) ,然后后面的每一项都多一个 \(w[i]\)

于是我们就可以将 \(f[i][j-v[i]]\) 加上 \(w[i]\) 转化成 \(f[i-1][j-v[i]]+w[i],f[i-1][j-2×v[i]]+2×w[i],...\)

再与 \(f[i-1][j]\) 比较一下最大值就行了

于是我们就得到了优化后的状态转移方程

\[f[i][j]=\left\{ \begin{align} &f[i-1][j],&j<w[i] \\ &max(f[i-1][j],f[i][j-v[i]]+w[i]),&j\geq w[i] \end{align} \right. \]
#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++) cin>>v[i]>>w[i];
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
        {
            if(j>=v[i]) f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]);
            else f[i][j]=f[i-1][j];
        }
            
    cout<<f[n][m];
    return 0;
}

我们可以发现,这个方程跟01背包非常像,所以我们可以按照01背包的思路优化成1维

注意完全背包跟01背包唯一的区别就在于01背包是 \(f[i-1][j-v[i]]+w[i]\) 而完全背包是 \(f[i][j-v[i]]+w[i]\)

完全背包没有 -1 这个问题,所以从小到大遍历,从大到小是不行的,从大到小会使得每个物品就拿一次(参见01背包的优化)

从大到小的话会导致 \(f[j]\)\(f[j-v[i]]\) 之前算,导致算 \(f[j]\) 的时候用的 \(f[j-v[i]\) 的值是 \(i-1\) 层的,变成01背包了

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

多重背包

Acwing 4 多重背包问题I

状态表示:二维

集合:所有只从前 \(i\) 个物品中选,并且总体积 \(\leq j\) 选法的集合

属性:每一个选法对应的总价值的最大值

状态计算和完全背包非常像,不过将 \(k\) 枚举结束的条件改成 \(s\)

不过这样的时间复杂度很高,\(O(m^2n)\)

#include <iostream>
using namespace std;
const int N = 105;
int f[N][N];
int v[N],w[N],s[N];
int main()
{
    int n,m;
    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++)
                f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k);
    cout<<f[n][m];
    return 0;
}

然后我们尝试优化,既然和完全背包很像,我们先来试试通过完全背包的方式优化

首先将原方程展开得:

\[f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-2×v[i]]+2×w[i],...,f[i-1][j-s[i]×v[i]]+s[i]×w[i]) \]

再对比 \(f[i][j-v[i]]\)

\[f[i][j-v[i]]=max(f[i-1][j-v[i]],f[i-1][j-2×v[i]]+w[i],...,f[i-1][j-s[i]×v[i]]+(s[i]-1)w[i],f[i-1][j-(s[i]+1)w[i]]+s[i]×w[i]) \]

我们发现两个式子很像,不过,\(f[i][j-v[i]]\) 多了一项 \(f[i-1][j-(s[i]+1)w[i]]+s[i]×w[i])\) ,我们不可以通过前 \(n\) 个数的最大值和最后一个数,求出前 \(n-1\) 个数的最大值,所以这样的方法是不可行的

我们可以用二进制的方式来优化
例如当 \(s=1023\) 时,我们真的需要从 \(0\) 枚举到 \(1023\) 吗?
我们可以将若干个 \(i\) 个物品打包,比如说我们可以打包成10组:\(1,2,4,8,...512\)
每组最多选一次,我们可以用这10组凑出来 \(0\)\(1023\) 的任何数(二进制可以表示出所有的十进制整数)

那么对于任何一个 \(s\),我们将物品打包成 \(2^0,2^1,2^2,...,2^k\)\(2^0\) 一直加到 \(2^k \leq s\)
如果 \(2^0\) 一直加到 \(2^k < s\),再补上一个 \(c\)
那么显然有 \(c<2^{k+1}\)
那么来证明一下是不是这样就能表示出 \(0\)\(s\) 的所有数?
首先,\(2^0\) 一直加到 \(2^k\) 能凑出 \(0\)\(2^{k+1}-1\) 的所有数(等比数列公式)
补上 \(c\) 以后便能凑出 \(0\)\(2^{k+1}+c\) 的所有数,\(2^{k+1}+c\)\(s\)
所以是可行的,我们将原本的 \(O(m^2 n)\) 优化到了 \(O(m^2 logn)\)
转化完之后再求一遍01背包就行了
注意我们的打包时数组体积的部分要开 \(log_2 2000×1000\) \(log_22000\) 要上取整得 \(12\)

#include <iostream>
using namespace std;
const int N = 25000;
int n,m;
int v[N],w[N],f[N];
int main()
{
    cin>>n>>m;
    int cnt=0; //重新打包后物品的个数
    for(int i=1;i<=n;i++)
    {
        int a,b,s;
        cin>>a>>b>>s;
        int k=1; //2的k次幂
        while(k<=s)
        {
            cnt++;
            v[cnt]=a*k;
            w[cnt]=b*k;
            s-=k;
            k*=2;
        }
        //补上的c
        if(s>0)
        {
            cnt++;
            v[cnt]=a*s;
            w[cnt]=b*s;
        }
    }
    n=cnt;
    for(int i=1;i<=n;i++)
        for(int j=m;j>=v[i];j--)
            f[j]=max(f[j],f[j-v[i]]+w[i]);
    cout<<f[m];
    return 0;
}

其实还有单调队列的优化方式,不过是提高的内容,等我之后滚回来更新

分组背包

Acwing 9 分组背包问题

状态表示:二维
集合:只从前 \(i\) 个物品里面选,且总体积 \(\leq j\) 的所有选法
属性:选法价值的最大值

状态计算:枚举第 \(i\) 组,物品选第几个或不选
不选也就是 \(f[i-1][j]\)
选第 \(k\) 个也就是 \(f[i-1][j-v[i][k]]+w[i][k]\)
注意 \(k\) 枚举的是下标,所以从0开始

#include <iostream>
using namespace std;
const int N = 105;
int f[N][N],v[N][N],w[N][N],s[N];
int n,m;
int main()
{
    cin>>n>>m;
    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=0;j<=m;j++)
        {
            f[i][j]=f[i-1][j];
            for(int k=0;k<s[i];k++)
            {
                if(v[i][k]<=j) 
                    f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
            }
        }  
    }
    cout<<f[n][m];
    return 0;
}

直接删去一维,得到状态转移方程

\[f[j]=max(f[j],f[j-v[i][k]]+w[i][k]),1\leq k\leq s[i] \]
#include <iostream>
using namespace std;
const int N = 105;
int f[N],s[N],v[N][N],w[N][N];
int n,m;
int main()
{
    cin>>n>>m;
    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)
                    f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
    cout<<f[m];
    return 0;
}

混合背包

Acwing 7 混合背包问题

此为几种背包的模型(01背包、多重背包、完全背包)的综合使用,所以如果弄懂了前几种背包模型,混合背包的问题也会迎刃而解,这里主要介绍一种写法使得代码可读性变高
首先我们知道多重背包实际上是转化为01背包来做的,所以我们如果遇到多重背包时,我们直接将它加进去,最后当作01背包解决就行了
在代码中,我使用了结构体,一个是物品的种类,另外两个是体积和权值
输入多重背包的时候,直接二进制优化好再加进 vector 就行了
最后根据物品类型的不同分别处理就行了文章来源地址https://www.toymoban.com/news/detail-644328.html

#include <iostream>
#include <vector>
using namespace std;
const int N = 1010;
int f[N];
struct Thing
{
    int kind,v,w;
};
vector<Thing> things;
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int v,w,s;
        cin>>v>>w>>s;
        if(s==-1) things.push_back({0,v,w});
        else if(s==0) things.push_back({1,v,w});
        else
        {
            int k=1; 
            while(k<=s)
            {
                things.push_back({0,v*k,w*k});
                s-=k;
                k*=2;
            }
            if(s>0) things.push_back({0,v*s,w*s});
        }
    }
    for(auto thing:things)
    {
        if(thing.kind==0)
        {
            for(int j=m;j>=thing.v;j--) 
            {
                f[j]=max(f[j],f[j-thing.v]+thing.w);
            }
        }
        else
        {
            for(int j=thing.v;j<=m;j++) 
            {
                f[j]=max(f[j],f[j-thing.v]+thing.w);
            }
        }
    }
    cout<<f[m];
    return 0;
}

到了这里,关于背包问题基础模型全解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

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

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

    2024年03月19日
    浏览(62)
  • C++基础:二维费用的背包问题

    注意:如果你还没搞定(指的是真正理解) 01背包 ,请 不要看 。看了脑壳更晕 什么是二维费用的背包问题?请看AcWing上的一道题: 有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。 每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。 求解将哪些物

    2024年02月09日
    浏览(40)
  • Python 算法基础篇:背包问题的动态规划解法

    背包问题是计算机科学中一个重要的组合优化问题,动态规划是解决该问题的高效算法技术。本篇博客将重点介绍背包问题的动态规划解法,包括状

    2024年02月16日
    浏览(39)
  • 算法基础复盘笔记Day09【动态规划】—— 背包问题

    ❤ 作者主页:欢迎来到我的技术博客😎 ❀ 个人介绍:大家好,本人热衷于 Java后端开发 ,欢迎来交流学习哦!( ̄▽ ̄)~* 🍊 如果文章对您有帮助,记得 关注 、 点赞 、 收藏 、 评论 ⭐️⭐️⭐️ 📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉 1. 题目

    2023年04月22日
    浏览(46)
  • AcWing算法学习笔记:动态规划(背包 + 线性dp + 区间dp + 计数dp + 状态压缩dp + 树形dp + 记忆化搜索)

    算法 复杂度 时间复杂度0(nm) 空间复杂度0(nv) 代码 算法 通过滚动数组对01背包朴素版进行空间上的优化 f[i] 与 f[i - 1]轮流交替 若体积从小到大进行遍历,当更新f[i, j]时,f[i - 1, j - vi] 已经在更新f[i, j - vi]时被更新了 因此体积需要从大到小进行遍历,当更新f[i, j]时,f[i - 1,

    2024年02月21日
    浏览(42)
  • 【算法设计与分析基础(第三版)习题答案】8.2 背包问题和记忆功能

    a. 对于下列背包问题的实例,应用自底向上动态规划算法求解。 b. a 中的实例有多少不同的最优子集 c. 一般来说,如何从动态规划算法所生成的表中判断出背包问题的实例是不是具有不止一个最优子集? 承重量 W = 6 物品 重量 价值 1 3 25 2 2 20 3 1 15 4 4 40 5 5 50 题目中要求使用

    2023年04月08日
    浏览(39)
  • React Hooks 全解:零基础入门

    你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗? ——拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function。 你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗? ——拥有了Hooks,生命周期钩子函数可以先丢一边了。 你在还在为组件

    2024年02月11日
    浏览(23)
  • ACWing算法基础课

    y总说 java不能用Scanner读入,要用Buffer.read();快十倍二十倍; y总19年5月的视频,牛13! 包括排序、二分、高精度、前缀和与差分、双指针算法、位运算、离散化、区间合并等内容。 一定要先移动end(就是把大数移到右边),后移动start; 否则 先找小数,会出现end start重合位置

    2024年02月13日
    浏览(43)
  • Acwing-语法基础练习

    目录 1. 非常基础的C++ (面向程序) 框架 2. 一些基础数据类型 3.变量的输入输出 4.ACWing题库-第1题:A+B 5.四则运算(只整理一部分较难的) 6.数据类型转换 寒假自学用,记录Acwing题目。 语言 :C++ 常见类型: bool 布尔类型: False(0) , True(1,或除了0外的值) char 字符: \\\'c\\\', \\\'a\\\' , \\\'n\\\'   

    2024年01月24日
    浏览(36)
  • 【数据库】MySQL基础知识全解

    系列综述: 💞目的:本系列是个人整理为了 秋招面试 的,整理期间苛求每个知识点,平衡理解简易度与深入程度。 🥰来源:材料主要源于拓跋阿秀、小林coding等大佬博客进行的,每个知识点的修正和深入主要参考各平台大佬的文章,其中也可能含有少量的个人实验自证。

    2024年02月09日
    浏览(51)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包