01背包问题(大彻大悟版)

  • Post author:
  • Post category:其他


背包问题身为一个非常经典的

动态规划

问题,理清思路很重要,在经过多次观看y总视频和b站解析,加上CSDN的文章辅助,我终于从很多不理解到大彻大悟,下面是我对于背包问题思路的总结,有问题的话欢迎指出。


谈到背包问题,先以下面这最经典的一道背包问题为例子


有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出最大价值。


输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第

i

i 件物品的体积和价值。


输出格式

输出一个整数,表示最大价值。


数据范围

0<N,V≤1000

0<vi,wi≤1000


输入样例

4 5
1 2
2 4
3 4
4 5


输出样例:

8


不废话,直接给出两种思路(可以结合看)



  1. bilibili表格法

b站中把背包问题转化为一个表格来理解,我觉得特别有用,对于背包问题光是靠说是很难理解的,通过表格总结公式和方法,

对于该题非常有效,入门背包问题必须要看看。


(链接:【【动态规划】背包问题】



https://www.bilibili.com/video/BV1K4411X766/?share_source=copy_web&vd_source=7e63f1b6fa68c511b241d12721f0a4bc




先从一个具体例子入手


对于所给定的背包容量和物品状态,我们需要找出价值最大的一个组合,通过画图

假设这是一个f(i,j)的数组,则

每一个f(i,j)的值都是前i(物品编号)件物品中,不超过j(背包容量)体积下的最大值

注意:


重中之重









每个格子是只考虑前i件物品







当时我比较不能理解的是为什么只考虑前i件物品下不超过该体积的值,编号i之后的物品不也可以放入该体积容量,假如该方法价值更高,那不是就不是该背包体积下最大价值了。


当时就是不能理解啊,后面思考之后终于悟了,

表格是所有情况都能考虑到,因为每个格子是在选了和不选的情况下取的最大值,我们只要只考虑前i件物品,后面每一个格子就能由前面的推出来,也能得到一个公式。

比如这题假如我们要算第f(4,8)的值,也就是该条件的最大价值,假设我们不知道该值,前面的数据我们都是知道的,

因此第4件物品有选和不选两种情况,


如果不选


,f(4,8)=f(3,8),本来需要考虑前4个,现在只需要考虑前3个,而f(3,8)前面是已经算出来了。


假如选


,则需要满足一个条件,即此时背包剩余的容量一定大于第四件物品体积,在此前提下,我们选了第4件物品,然后往前找,选了4之后我们表格应该找到f(3,8-第四件物品体积)也就是f(3,3),此时只考虑前3个物品,即f(4,8)=第四件物品价值+f(3,3),因为前面的f都是已经计算出来对应条件的价值最大值了,f(4,8)的值也就出来了。


最后,在选和不选中取一个最大值即使f(4,8)的值。

用表格容易理解,但确定是该方法不容易运用到其他同题型中,下面给出第二种


2.

闫式DP分析法

此方法比较有规律和逻辑,但对于新手不容易理解(nnd当时我看了好多遍才懂),但是第一种懂了,第二种也应该学习,因为该方法会了之后同题型就变得很简单了,后面我也会发类似题目的博客,用该方法来做,会发现简单很多。

大致思路与第一种是差不多的,也是把f(i,j)分为两种情况,这里不多做解释,和第一种同理


该方法很系统的对于该类题型的做题方法做了一个总结,此时f(i,j)为一个集合,每个f(i,j)的内容需要其属性来判断,背包问题要求我们找最大值,即属性为max,因此f(i,j)为对应条件下的最大值,然后进行状态计算,如何计算f(i,j),找到最后一个不同点进行划分,对于其他题型我们只需要对f(i,j)进行不同情况的划分考虑完就可以了。

状态计算的核心:


如何将现有的集合划分为更小的子集,使得所有子集都可以计算出来



,这是我们需要做的事

说了这么多,没有代码就是空谈


代码如下:(第一种和第二种方法都一样代码)

#include<iostream>
using namespace std;
const int N=1010;
int f[N][N];
int v[N],w[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    
    for(int i=1;i<=n;i++)//i代表物品,第一件开始考虑
        for(int j=0;j<=m;j++)//j代表背包容量,可以为0(一件都不选),因此从0开始
        {
            f[i][j]=f[i-1][j];//不选第i个物品
            
            if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);//选第i个物品
        }
    cout<<f[n][m]<<endl;
    return 0;
}

对于该代码我们是可以进行优化的,把二维转化为一维可以节省空间和时间,这里也给出优化后的代码,想要了解为什么的这里也给出一个链接

https://www.acwing.com/solution/content/1374/

,里面解释的非常好,这里我就不多阐明


优化后的代码:

#include<iostream>
using namespace std;
const int N=1010;
int f[N];
int v[N],w[N];
int main()
{
    int n,m;
    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=m;j>=v[i];j--)//这里为什么从后往前遍历,因为为了防止第i层前面先被更新
        {
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    cout<<f[m]<<endl;
    return 0;
}

最后祝大家早日理解背包问题,把动态规划用的行云流水,以后也会常更新dp的!!!



版权声明:本文为m0_74012728原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。