欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

DP - 背包九讲之完全背包

程序员文章站 2022-07-12 09:30:54
...

完全背包问题

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

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

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

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

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。

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

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10

学习完全背包时一定要先学会01背包,01背包传送门
DP - 背包九讲之01背包

问题大意:

完全背包问题和01背包问题很相似,给出n个物品和容量为m的背包,每件物品都有他的体积和价值,每件物品可以拿0次和无限次,询问背包内可以容纳的最大价值是多少。

问题分析:

我们知道在01背包中:每件物品只有拿和不拿两种选择,但是在完全背包中,每件物品可以拿最多无数次,这样要怎么把状态表示出来呢,如果按照01背包的思路,我们在第二重循环中只要枚举要几个就可以了,一直到容积的最大值,贴代码(错误代码):
状态转移方程:
dp[j]=max(dp[j],dp[j-v[i]]+w[i])

#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e3 + 50;
int dp[N], w[N], v[N];
int main()
{
    memset(dp, 0, sizeof dp);//每个元素初始化0
    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--)// 01背包的第二维
        for (int k = 0; k * v[i] <= j; k++)//枚举到底拿几个物品
          if (j - k * v[i] >= 0)
            dp[j] = max(dp[j], dp[j - k * v[i]] + k * w[i]);
    cout << dp[m] << endl;
    return 0;
}

但是不难发现,这份代码一定会TLE,数据范围是1000,而这个时间复杂度为1000 x 1000 x 1000接近1e9了,一定会超时的。
换种思路。
01背包就地滚动,每一次转移都用的第 i 个物品没算入背包中的状态,第二维是从m -> v[i] 的,当前算的是放入还是不放入第 i 个物品时的最大价值,我们只要把顺序改成 v[i] -> m 即可,表示的就是第 i 个物品被算过了,并且可以忽略掉应该选几个。为什么把顺序变一下就可以实现呢,分析一下状态转移方程。
dp[j]=max(dp[j],dp[j-v[i]]+w[i]) 在完全背包中,第二维是 v[i] -> m 当前状态是由 dp[j-v[i]]+w[i] 转移过来的,但是循环顺序是从v[i] -> m,也就是说,到m之前,这个状态是已经被算过的,即:第 i 个物品已经被考虑过选还是不选,选几个了。代码实现:

#include <iostream>
using namespace std;
const int N = 1e3 + 50;
int dp[N], w[N], v[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 = v[i]; j <= m; j++)//修改第二维即可实现
        dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
    cout << dp[m] << endl;
    return 0;
}