多重背包问题

多重背包问题

前面的01背包问题中,每种商品(物品)只有一件,而当遇到每种物品超过一件的情况叫做多重背包问题。
下面请看这道编程题:

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

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

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

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

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

数据范围
0<N≤1000
0<V≤2000
0<vi,wi,si≤2000

提示:
本题考查多重背包的二进制优化方法。

输入样例

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

输出样例:

10

说明,以上这道题来自于AcWing网站,地址:https://www.acwing.com/problem/content/description/5/
通常,我们的思路是把多重背包问题转化为01背包问题来做,首先想到的就是将多个重复的商品摊开来形成多个体积和价值都相同的商品,然后使用01背包问题解决。但是在这道题中,注意商品数N和每件商品的重复测试上限均超过1000,因而如果使用上述方法转换则会使复杂度达到1000*2000*2000 = 4*10^9,因而肯定会超时,下面给出我写出的以上思路的代码以供参考。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(){
    int numObject,numVolume;
    cin>>numObject>>numVolume;
    int numIn = 0;
    vector<vector<int> > ObjectP(numObject,vector<int>(3));  //创建一个numObject*3的矩阵
    for(int i=0;i<numObject;i++){
        for(int j=0;j<3;j++){
            int in;
            cin>>in;
            ObjectP[i][j] = in;
            if(j==2){
                numIn += in;
            }
        }
    }
    vector<vector<int> > ObjectV(numIn,vector<int>(numVolume));  //创建一个二维矩阵,存放满足条件的最大价值递推矩阵
    int count = 0;
    for(int j=0;j<numVolume;j++){
        count = 0;
        for(int i=0;i<numObject;i++){
            for(int k=0;k<ObjectP[i][2];k++){
                if(count==0)
                {
                    if(ObjectP[i][0]<=j+1){
                        ObjectV[count][j] = ObjectP[i][1];
                    }
                    else
                    {
                        ObjectV[count][j] = 0;
                    }
                }
                else
                {
                    if(ObjectP[i][0]<=j+1){
                        ObjectV[count][j] = max(ObjectV[count-1][j],ObjectV[count-1][j-ObjectP[i][0]]+ObjectP[i][1]);
                    }
                    else{
                        ObjectV[count][j] = ObjectV[count-1][j];
                    }
                }
                count++;
            }
            
        }
    }
    cout<<ObjectV[count-1][numVolume-1];
}

本人编程能力有限,代码实现的有点长,弄了半天还超时了,没有通过全部测试。因而要解决这个问题必须考虑一些优化方法。

下面介绍本题的改进方法,二进制优化方法。
在本题中,如果能把s个i物品分解成一些不重复的几个i物品的组合就好了。即将一个数si分解成P个数,要求这P个数的任意组合可以组成比这个数小的任意数(0~si),求P的最小值是多少?

将一个数si分解成P个数,要求这P个数的任意组合可以组成比这个数小的任意数(0~si),求P的最小值是多少?

我们可以发现当si = 0的时候,P=0;(P= log1)
si = 1, P=1; (1) (P = log2)
si = 2, P=2; (1,2) (P=[log3])
si = 3, P=2; (1,2) (P = log4)
si = 4, P=3; (1,2,3) (P=[log5])
...
其中[]表示向上取整。

因而对于si件物品i,最小可以分解成[log(si+1)]件物品,因而减少了分解后物品的数目,从而可以提高运算效率。
依据以上思想进行编程,代码如下:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;


struct good{
    int v,w;    //分别存储物品体积和价值
};

int main(){
    vector<good> goods;
    int f[2001] = {0};
    int n, m;    //分别表示物品种数和背包容量
    cin >> n >> m;
    for(int i = 0; i < n; i++){
        int v, w, s;
        cin >> v >> w >> s;
        for(int j = 1; j <= s; j *=  2){
            s -= j;
            goods.push_back({v * j,w * j});
        }
        if(s > 0){
            goods.push_back({v * s, w * s});
        }
    }
    
    for(auto go : goods){
        for(int k = m;k >=go.v; k--){
            f[k] = max(f[k], f[k-go.v]+go.w);
        }
    }
    cout << f[m];
    return 0;
}

以上代码的运行时间363ms,满足要求。

posted @ 2019-08-27 16:13  林深处见鹿  阅读(418)  评论(0编辑  收藏  举报