AcWing 3 完全背包问题

题目描述:

有 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背包问题是n个物品,每个物品要么不选,要么每种物品只能选一次。完全背包问题是每个物品可以不选,也可以选无数次,只要在背包容量内都可以接受。与01背包类似,完全背包问题物品的属性同样是数量,体积和价值,要求的最优解同样是最大价值,因此不必改变状态表示方式,f[i][j]还是表示在前i个物品中选择总体积不超过j的物品能获得的最大价值。

01背包问题是要考虑第i个物品选还是不选的问题,完全背包问题则需要考虑第i个物品选多少的问题,设第i个物品选了k个得到f[i][j],则前i-1个物品中应该已经选了体积为j - k*v[i]的物品,k从0到j / v[i]。即f[i][j] = max{f[i-1][j-k*v[i]] + k*w[i]}。也就说,f[i][j]的状态是由前i-1个物品中k + 1种状态经过比较转移过来的。实现状态转移需要三重循环:第一层是i的枚举,第二层是j的枚举,第三层是k的枚举。代码如下:

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1005;
int w[maxn],v[maxn],f[maxn][maxn];
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 = 0;j <= m;j++)
            for(int k = 0;k * v[i] <= j;k++)    f[i][j] = max(f[i][j],f[i - 1][j - k * v[i]] + k * w[i]);
    cout<<f[n][m]<<endl;
    return 0;
}

虽然上面的状态转移方程不难推出,但是每个状态都需要之前的k + 1个状态比较才能转移过来,效率十分低下,其中仍饱含着诸多的重复计算。f[i][j]和f[i][j-v[i]]都利用了上一层的若干个状态,不妨列出来:

f[i][j]=max(f[i-1][j],f[i-1][j-v]+w,…,f[i-1][j-kv]+kw);

f[i][j-v]=max(        f[i-1][j-v],…,    f[i-1][j-kv]+(k-1)w);

观察知f[i][j]仅比f[i][j-v]多利用了一个f[i-1][j]的状态,其它利用的状态都是都是完成一致的,不妨将上面的两个等式合并起来,可以得到f[i][j] = max(f[i-1][j],f[i][j-v] + w),这样一来,f[i][j]就只需要判断两个状态就可以得出了,效率大大提升。01背包问题的状态转移方程是f[i][j] = max(f[i-1][j],f[i-1][j-v] + w),可见与完全背包问题的状态转移方程f[i][j] = max(f[i-1][j],f[i][j-v] + w)是相当类似的,写法自然也只有些许的差异。完全背包问题状态转移方程的优化告诉我们得出了一个状态转移方程后,如果效率低下,则应该继续观察其中的重复运算,一种状态固然可以由其它状态转移过来,但是状态转移方程却不一定是唯一的。比如求数组的最大值,固然可以写成数组每个元素的最大值,也可以写成前n-1个元素的最值与最后一个元素中的较大者。

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1005;
int w[maxn],v[maxn],f[maxn][maxn];
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 = 0;j <= m;j++){
            f[i][j] = f[i - 1][j];
            if(j >= v[i])    f[i][j] = max(f[i][j],f[i][j - v[i]] + w[i]);
        }
    cout<<f[n][m]<<endl;
    return 0;
}

下面继续用滚动数组来优化完全背包问题。

f[4][5]是由f[3][5]和f[4][1]转移而来的,在01背包问题中,自左而右的dp数组填充顺序会覆盖掉还需要使用的状态值,所以我们需要自右而左填充数组。但是在完全背包问题中,每个状态仅由上面的一个状态和左边的一个状态转移而来,左边的状态就是当前行的状态,是新值,上面的状态只有正在计算的状态计算好了才会被覆盖,所以可以自左而右填充数组,而不用担心还需要使用的数据被覆盖掉。

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1005;
int w[maxn],v[maxn],f[maxn];
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++){
            f[j] = max(f[j],f[j - v[i]] + w[i]);
        }
    cout<<f[m]<<endl;
    return 0;
}
发布了272 篇原创文章 · 获赞 26 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_30277239/article/details/103844891
今日推荐