单调队列优化多重背包 && 洛谷 P1776 宝物筛选

传送门


多重背包

学了这么多年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;
}