多重背包问题二进制优化

先来看下多重背包问题:

\(N\)种物品和一个容量是\(V\)的背包。

\(i\)种物品最多有\(s_i\)件,每件体积是\(v_i\),价值是\(w_i\)

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

输入格式

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

接下来有\(N\)行,每行三个整数\(v_i,w_i,s_i\)用空格隔开,分别表示第\(i\)种物品的体积、价值和数量。

输出格式

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

数据范围

\(0<N,V≤100\)
\(0<v_i,w_i,s_i≤100\)

输入样例

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

输出样例:

10

分析

如果使用朴素方法三个循环来实现,这个时间复杂度是\(O(N*S*V)\)对于上述的数据输入是可行的,对于更大的输入可以使用二进制优化来实现。我们可以看到在这里比较费时的一个环节就是\(s_i\)的个数的枚举,我们设法消去这个循环。二进制优化的思想就是消去这个循环并将这个多重背包问题转换为01背包问题。如果要把这个\(s_i\)个枚举变换成01背包问题,那么我们需要把\(0-s_i\)中的所有的数都使用01背包问题中的多个物体的加和来表示。下面举例来说明:

列如多重背包问题中的某一个元素是v(占用的体积)=1,w(权重)=2,s(个数)=200;那么对于这个元素,200可以用(1, 2, 4, 8, 16……64, 73)这几个数的加和来表示,进而转换成这几个数的01背包问题。这里需要保证的就是我们假定的这个(1, 2,4……64, 73)数组可以取出其中的几个并且保证他们的加和为200:

如此证明,现有(0-1),两边加上2,得(2-3)(表示我们可以通过加和得到0~3中的所有数),两边再加上4,得(4-5),(6-9),依次往后推到,这个数组必定可以表示200,那么最后这个73是因为(1,2,4……64)可以表示0-127之间的所有数,若有128则超出了,故用73,显然(0-127)两边加上73可以得到(0-200)。

实现

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+5; 
int v[N],w[N],dp[N];
int main(){
    int n,m;
    cin>>n>>m;
    int cnt=0;
    for(int i=1;i<=n;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        int k=1;
        while(k<=c){
            v[++cnt]=a*k;
            w[cnt]=b*k;
            c-=k;
            k*=2;
        }
        if(c>0){
            v[++cnt]=a*c;
            w[cnt]=b*c;
        }
    }
    for(int i=1;i<=cnt;i++){
        for(int j=m;j>=v[i];j--){
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    cout<<dp[m];
}
posted @ 2021-02-08 13:39  某柯学的  阅读(240)  评论(0)    收藏  举报