多重背包单调队列
考虑思考一下 \(f\) 最暴力的转移。设 \(v,w,s\) 为当前处理的物品的体积、价值、数量。然后体积 \(j\mod v=r\)。显然是 \(f[i][j]=\min f[i-1][j],f[i-1][j-v]+w,f[i-1][j-2v]+2w,f[i-1][j-3v]+3w,\dots,f[i-1][j-sv]+sw\)。然后写一下下一项 \(f[i][j-v]=\min f[i-1][j-v],f[i-1][j-2v]+w,f[i-1][j-3v]+2w,f[i-1][j-4v]+3w,\dots,f[i-1][j-(s+1)v]+sw\)。以此类推,发现每一项都在前面少了一项,最后一项当然是 \(f[i][r]\)。发现这些递推式都是由 \(f[i-1]\) 这一层的所有值构成的,可以看作长度为 \(s+1\) 的滑动窗口。我们可以直接按照余数 \(0\le r<v\) 来处理,一类一个滑动窗口处理即可。时间复杂度恰好是循环 \(n\) 次,每次都是以余数遍历,仔细分析内部两重循环其实是 \(O(m)\),总的就是 \(O(nm)\),相比于二进制优化又有了提升。
#include<cstdio>
#define max(x,y) ((x)>(y)?(x):(y))
const int N=20001;
int n,m,f[N],g[N],q[N];
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<n;++i){
int v,w,s;
scanf("%d%d%d",&v,&w,&s);
for(int j=0;j<=m;++j)g[j]=f[j];
for(int j=0;j<v;++j){//注意这个两重循环是 O(m),因为每个余数都枚举到了,只是整合成一块了。
int hh=0,tt=-1;
for(int k=j;k<=m;k+=v){//单调队列优化
if(hh<=tt&&q[hh]<k-s*v)++hh;
if(hh<=tt)f[k]=max(f[k],g[q[hh]]+(k-q[hh])/v*w);
while(hh<=tt&&g[q[tt]]-(q[tt]-j)/v*w<=g[k]-(k-j)/v*w)--tt;
q[++tt]=k;
}
}
}
printf("%d",f[m]);
return 0;
}