AcWing 4 多重背包问题 I

题目描述:

有 N 种物品和一个容量是 V的背包。第 i种物品最多有 si 件,每件体积是 vi,价值是 wi。

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

输入格式

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

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

输出格式

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

数据范围

0<N,V≤100
0<vi,wi,si≤100

输入样例

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:

10

分析:

在完全背包问题中,每种物品有无限件可以取,而多重背包问题每种物品只有有限件。设第i个物品选了k个得到f[i][j],则前i-1个物品中应该已经选了体积为j - k*v[i]的物品,即f[i][j] = max{f[i-1][j-k*v[i]] + k*w[i]}。这里的k不仅要满足k * v[i] <= j,而且要小于题目给定的数量限制si,因此我们不难得出与完全背包问题最开始的算法类似的算法,只是在k的限制上多了一个不超过s。i

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 105;
int w[N],v[N],s[N],f[N][N];
int main(){
    int n,m;
    cin>>n>>m;
    for(int i = 1;i <= n;i++)   cin>>v[i]>>w[i]>>s[i];
    for(int i = 1;i <= n;i++)
        for(int j = 0;j <= m;j++)
            for(int k = 0;k * v[i] <= j && k <= s[i];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;
}

复杂度分析:时间复杂度为O(nms),对于本题的小范围数据,是可以解决的。但是我们无法使用类似于完全背包问题的优化策略来优化状态转移方程。这是因为:

先回忆下完全背包问题中内层循环

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]);

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]时,k <= j / v;即边界情况是第i个物品选k个,这里因为有无限多个物品。此时的k = j / v;而求f[i][j-v]时,(j - v) / v = j / v - 1 = k - 1,此时第i个物品最多只能选k-1个,所以最后一项是加上(k-1)w。

而本题k * v[i] <= j && k <= s[i],k < min(j / v,s),如果j / v < s的话就相当于完全背包问题了,不妨考虑j / v > s的情况,即第i个物品可以再装下去但是被s[i]限制了。也就是说求f[i][j]的边界情况是第i个物品放s个,对于f[i][j-v]而言,由于限制第i个物品的不是容量而是数量,所以第i个物品仍可以选s个,因此最后一项是要加上sw。即:

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

f[i][j-v]=max(        f[i-1][j-v],…,    f[i-1][j-sv]+(s-1)w) ,f[i-1][j-(s+1)v]+sw;

观察到f[i][j]使用了f[i-1][j]到f[i-1][j-sv]这么多状态,而f[i][j-v]使用了f[i-1][j-v]到f[i-1][j -(s+1)v],由于f[i][j-v]用到的最后一种状态f[i-1][j -(s+1)v],f[i][j]没有使用,故不能由f[i][j-v]转移到f[i][j],比如一个是1 2 3 4的最值,另一个是2 3 4 5的最值,二者固然有重合部分,却不能通过简单地大小比较去判断二者最值的关系。但是可以使用单调队列去优化多重背包问题,这里暂时不做阐述,使用二进制优化多重背包问题的思路将在下一题详细讲解。当然,本题的思路也可以使用滚动数组实现,将j自大而小遍历即可。

发布了272 篇原创文章 · 获赞 26 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_30277239/article/details/103860691