传送门
多重背包
学了这么多年oi竟然只知道二进制拆分做法,白学了
多重背包就是每个物品有个数限制的01背包。
怎么做呢?
最暴力的是把每个物品拆成m[i]个物品,做01背包。
这样时间显然会爆炸。
二进制拆分优化
于是可以利用二进制性质,拆的时候打个包。
对于每种物品来说,每 (1,2,4…2^k) 个物品看做一个物品,最后再加上 (m-2^{k+1}) 这个物品,一共组成了 (k+1) 个物品。
而这 (k+1) 个数字正好能组成 (1) 到 (m) 的所有数字。
于是就成了log的复杂度。
单调队列优化
这个log也是可以避免的。
这里用到单调队列优化。
我们发现对于给定的一个物品i,其体积大小w是确定的,所以对于每一个枚举的容量j,一定是从 (dp[j-k imes w]) 转移过来,其中k的范围为 (0<=k<=m[i])。
于是我们发现,可以把 j 按照 j%w 的值进行分类,即在转移时一定是余数相同的j之间进行转移。
这样对于每一个余数开一个单调队列,就可以快乐地使用单调队列优化了。
AC代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=100005;
long long ans,dp[maxn],v[maxn],w[maxn],m[maxn],n,maxm;
int main(){
ios::sync_with_stdio(false);
cin>>n>>maxm;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>m[i];
for(int i=1;i<=n;i++){
for(int d=0;d<w[i];d++){
deque<int> q;
for(int k=0;k<=m[i];k++){
int j=maxm-d-k*w[i];
if(j<0) break;
while(!q.empty()&&dp[q.back()]-v[i]*(q.back()/w[i])<dp[j]-v[i]*(j/w[i])) q.pop_back();
q.push_back(j);
}
for(int j=maxm-d;j>=0;j-=w[i]){
while(!q.empty()&&j-m[i]*w[i]>=0&&dp[q.back()]-v[i]*(q.back()/w[i])<dp[j-m[i]*w[i]]-v[i]*((j-m[i]*w[i])/w[i])) q.pop_back();
if(j-m[i]*w[i]>=0) q.push_back(j-m[i]*w[i]);
dp[j]=max(dp[j],dp[q.front()]+(j-q.front())/w[i]*v[i]);
while(!q.empty()&&q.front()>=j) q.pop_front();
}
}
}
for(int i=1;i<=maxm;i++) ans=max(ans,dp[i]);
cout<<ans;
return 0;
}