多重背包问题二进制优化
先来看下多重背包问题:
有\(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];
}