AcWing 2. 01背包问题(模板)

题目链接:点击这里
在这里插入图片描述
在这里插入图片描述
01背包问题描述:

N N 件物品和一个容量为 V V 的背包。放入第 i i 件物品耗费的费用是 C i C_i ,得到的价值是 W i W_i 。求解将哪些物品装入背包可使价值总和最大。

这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放

用子问题定义状态:即 F [ i , v ] F[i, v] 表示前 i i 件物品恰放入一个容量为 v v 的背包可以获得的最大价值。

则其状态转移方程便是: F [ i , v ] = m a x { F [ i 1 , v ] , F [ i 1 , v C i ] + W i } F[i, v] = max\left\{F[i − 1, v], F[i − 1, v − C_i] + W_i\right\}

这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。

所以有必要将它详细解释一下:“将前 i i 件物品放入容量为 v v 的背包中”这个子问题,若只考虑第 i i 件物品的策略(放或不放),那么就可以转化为一个只和前 i 1 i − 1 件物品相关的问题。

  1. 如果不放第 i i 件物品,那么问题就转化为“前 i 1 i − 1 件物品放入容量为 v v 的背包中”,价值为 F [ i 1 , v ] F[i − 1, v]
  2. 如果放第 i i 件物品,那么问题就转化为“前 i 1 i − 1 件物品放入剩下的容量为 v C i v − C_i 的背包中”,此时能获得的最大价值就是 F [ i 1 , v C i ] F[i − 1, v − C_i] 再加上通过放入第 i i 件物品获得的价值 W i W_i

注意到 F [ i , v ] F[i,v] 只与之前的状态 F [ i 1 , . . . ] F[i-1, ...] 有关,所以可以枚举 i i 1 1 N N v v 0 0 V V ,通过边界 F [ 0 , 0... V ] = 0 F[0, 0...V] = 0 (即前 0 0 件物品放入任何容量的背包中都只能获得价值 0 0 )就可以把整个 F F 数组递推出来。

#include<iostream>
#include<algorithm>

using namespace std;
const int N = 1010;

int v[N], w[N];
int f[N][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][j], f[i-1][j-v[i]] + w[i]);
        }
    }
    
    cout<<f[n][m]<<endl;
    
    return 0;
}

以上方法的时间和空间复杂度均为 O ( V N ) O(VN) ,其中时间复杂度应该已经不能再优化了,但空间复杂度却可以优化到 O ( V ) O(V)

先考虑上面讲的基本思路如何实现,肯定是有一个主循环 i 1... N i ← 1 . . . N ,每次算出来二维数组 F [ i , 0... V ] F[i, 0 . . . V] 的所有值。

那么,如果只用一个数组 F [ 0... V ] F[0 . . . V] ,能不能保证第 i i 次循环结束后 F [ v ] F[v] 中表示的就是我们定义的状态 F [ i , v ] F[i, v] 呢?

F [ i , v ] F[i, v] 是由 F [ i 1 , v ] F[i − 1, v] F [ i 1 , v C i ] F[i − 1, v − C_i] 两个子问题递推而来,能否保证在推 F [ i , v ] F[i, v] 时(也即在第 i i 次主循环中推 F [ v ] F[v] 时)能够取用 F [ i 1 , v ] F[i − 1, v] F [ i 1 , v C i ] F[i − 1, v − C_i] 的值呢?

事实上,这要求在每次主循环中我们以 v V . . . 0 v ← V . . . 0 的递减顺序计算 F [ v ] F[v] ,这样才能保证计算 F [ v ] F[v] F [ v C i ] F[v − C_i] 保存的是状态 F [ i 1 , v C i ] F[i − 1, v − C_i] 的值。

#include<iostream>
#include<algorithm>

using namespace std;
const int N = 1010;

int v[N], w[N];
int f[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 = m; j >= v[i]; --j)
        {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    
    cout<<f[m]<<endl;
    
    return 0;
}

【补充】

我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。

有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。

一种区别这两种问法的实现方法是在初始化的时候有所不同。

  1. 如果是第一种问法,要求恰好装满背包,那么在初始化时除了 F [ 0 ] F[0] 0 0 ,其它 F [ 1.. V ] F [1..V ] 均设为 −∞ ,这样就可以保证最终得到的 F [ V ] F [V ] 是一种恰好装满背包的最优解。

  2. 如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将 F [ 0.. V ] F [0..V ] 全部设为 0 0

这是为什么呢?

可以这样理解:初始化的 F F 数组事实上就是在没有任何物品可以放入背包时的合法状态。

  1. 如果要求背包恰好装满,那么此时只有容量为 0 0 的背包可以在什么也不装且价值为 0 0 的情况下被“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,应该被赋值为 -∞ 了。

  2. 如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为 0 0 ,所以初始时状态的值也就全部为 0 0 了。

这个小技巧完全可以推广到其它类型的背包问题。

发布了811 篇原创文章 · 获赞 127 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/qq_42815188/article/details/104852010