问题描述:给出背包大小,给出一些物品,每个物品有一个重量、价值、个数,求能装进背包的最大总价值。
我们知道,这样的问题有很多种解法,比如可以用二进制拆分来使每个物品的个数变成原来的log,但是,这样显然还不够,下面我们来讨论利用队列来将多重背包优化到O(n*allv)的复杂度。
我们知道,背包问题的转移方程为 f[i][j]=max(f[i−1][j],f[i−1][j−w[i]]+p[i])
如果将背包大小按模当前物品的重量分类,可以发现, f[i][j] 可由与它模当前物品的重量相同的背包大小转移得,并且当前背包大小与前一状态相差的物品数必须小于等于当前物品的个数。这样,就把问题转化成了求以当前背包大小为末端点的某一段固定长度的区间中的最优解,这就是经典的滑动窗口问题,可以用队列优化到线性的复杂度,简单来说,以求区间最大值为例,就是用队列维护一个单调降的队列,每次的最优解就是队列头,每次有新的节点加入时从队尾开始,把所有不优于它的节点删除。
那么如何判断两个状态那个更优?假设i状态优于j,now为当前的背包大小。
f[i]+(now−i)/w∗p>f[j]+(now−j)/w∗p
f[i]−i/w∗p>f[j]−j/w∗p
所以我们得到,f[i]-i/w*p越大的状态越优。
最后来估计复杂度:每个物品放一次,每个物品的每个剩余系放一次,每个剩余系放allv/w次,所以复杂度为 O(n∗allv/w∗w)=O(n∗allv) 。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 2006
#define maxv 506
using namespace std;
struct data{
int w,p,m;
}a[maxn];
int n,allv,hed,tal,f[maxn][maxv],que[maxv];
inline char nc(){
static char buf[100000],*i=buf,*j=buf;
return i==j&&(j=(i=buf)+fread(buf,1,100000,stdin),i==j)?EOF:*i++;
}
inline int _read(){
char ch=nc();int sum=0;
while(!(ch>='0'&&ch<='9'))ch=nc();
while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
return sum;
}
int main(){
freopen("hallows.in","r",stdin);
freopen("hallows.out","w",stdout);
n=_read();allv=_read();
for(int i=1;i<=n;i++)a[i].m=_read(),a[i].w=_read(),a[i].p=_read();
for(int i=1;i<=n;i++)
for(int s=0;s<a[i].w;s++){
que[hed=tal=1]=s;f[i][s]=f[i-1][s];
for(int j=s+a[i].w;j<=allv;j+=a[i].w){
while(hed<=tal&&(((j-que[hed])/a[i].w)>a[i].m))hed++;
f[i][j]=max(f[i-1][j],f[i-1][que[hed]]+((j-que[hed])/a[i].w)*a[i].p);
while(hed<=tal&&(f[i-1][j]-j/a[i].w*a[i].p>=f[i-1][que[tal]]-que[tal]/a[i].w*a[i].p))tal--;
que[++tal]=j;
}
}
printf("%d",f[n][allv]);
return 0;
}