上上期我们讨论了多重背包的朴素做法。
时间复杂度显然很高。所以优化是很必要的。
废话不多说,说一下优化思路。
还是这道题:P1776
我们想一下,我们当时用k从s[i]枚举到0是否必要。
我们其实可以合并一些物品。
所以我们可以只枚举一些物品,通过这些物品互相合并,产生新的物品。比如:10=1+2+4+3
那为什么不拆成1+2+7呢?因为这样你就无法合成出4了。
那怎么枚举呢?这里要用到一点倍增的思想了。
给大家看一下局部代码:
相信你看了代码一定秒懂。
int a,b,s;//a是重量,b是价值,s是数量
cin>>a>>b>>s;
//如何合成这些多余的物体
int k=1;
while(k<=s){
w[++cnt]=k*a;//合成的重量
v[cnt]=k*b;//合成的价值
s-=k;//物品数相应减少
k*=2;//翻倍
}
if(s){//没办法翻倍了
w[++cnt]=s*a;
v[cnt]=s*b;
//剩下的自动合成
}
这样就OK了。
最后是完整代码:
//由于直接枚举取多少个会TLE
//所以我们进行二进制优化
//思考一下,其实我们没必要枚举0-s
//我们可以不断倍增:1,2,4,8,16,32,64,128...
//取这些数量个物品
//这样,通过不断组合可以组合出所有种类
//比如:10=1+2+4+3
//为什么有个3呢?
//因为正好剩个3了
//靠1,2,4,3可以组成0-10的所有数
//5=1+4 6=2+4 7=3+4 8=3+4+1 9=2+3+4 10=1+2+3+4
//所以在枚举时,我们不断倍增就可以了
//这样可以大大减少枚举量
#include <bits/stdc++.h>
using namespace std;
int w[maxn],v[maxn],dp[maxm];
int main(){
int n,m;
cin>>n>>m;
int cnt=0;//记录新物体数(相当于优化后的n)
for(int i=1;i<=n;i++){
int a,b,s;
//a是重量,b是价值,s是数量
cin>>a>>b>>s;
//接下来是重中之重
//如何合成这些多余的物体
int k=1;
while(k<=s){
w[++cnt]=k*a;//合成的重量
v[cnt]=k*b;//合成的价值
s-=k;//物品数相应减少
k*=2;//翻倍
}
if(s){//没办法翻倍了
w[++cnt]=s*a;
v[cnt]=s*b;
//剩下的自动合成
}
}
//0-1背包
for(int i=1;i<=cnt;i++){
for(int j=m;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
cout<<dp[m]<<endl;
return 0;
}
友情提醒:请不要Ctrl C+Ctrl V,这段代码有问题
那么还有别的优化方式吗?当然有!
下期我们来讨论一下非常变(简)态(单)单调队列优化。