DP - 背包九讲之完全背包

完全背包问题

有 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;
}
posted @ 2020-07-14 17:19  Hayasaka  阅读(92)  评论(0编辑  收藏  举报