男人八题之一
对于完全背包的优化,我们发现他是求所有前缀中的最大值,所以用一个变量来更迭,只是这个变量恰好是f[i,j-v]+w
而对于多重背包的优化,我们要求的是中间一段的最大值,就算我们知道某几个确定元素和整体的最大值
由于存在不等关系,也无法知道区间内的最大值,所以无法使用完全背包问题的优化方式
但是通过对比f[i,j],f[i,j-v],f[i,j-2v],f[i,j-3v]...,f[i,j-kv]
我们可以发现j,j-v,j-2v,...,j-kv都有一个特点,就是模v的余数r相同(也就是j一直减v,最后不够减去v的体积)
这样就用r把j,j-v,j-2v,…,j-kv这些数从后面最小的j-kv转化为到了[r,r+v,r+2v,...,j-v,j]
求f[i,j]就是求[r,r+v,r+2v,…,j-v,j]中包含j-v往前的s个数的最大值,取不到s个就特判成有几个取几个,f[i,j-kv]也是以此类推
这种数据结构就对应了基础课的单调队列
所以多重背包的优化就是求长度固定为s的滑动窗口中的最大值,但因为每个f[i,j-kv]之间存在一个偏移量,所以还要加上一个关于w的偏移量
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=20010;
int f[N];
int g[N];//用滚动数组存储上一层的f[i];
int q[N];//队列
int n,m;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int v,w,s;
cin>>v>>w>>s;
memcpy(g,f,sizeof f);//先把枚举前的那一层状态存下来
for(int r=0;r<v;r++)//枚举余数,最小是0(表示m%v==0),最大<v(表示kv->m,但是小于m)
{
int hh=0,tt=-1;//定义单调队列的头和尾
for(int k=r;k<=m;k+=v)//枚举坐标轴上的值,从余数r开始,到放不下结束
{
if(hh<=tt && q[hh]<k-s*v) hh++;
//下标都是v的整数倍,如果k-s*v>q[hh],说明q[hh]这个数已经在窗口外面,hh++让它出队
if(hh<=tt) f[k]=max(f[k],g[q[hh]]+(k-q[hh])/v*w);//求的是窗口内部最大值
//q[hh]就是最大值的下表,所以g[q[hh]]就是f[]的最大值,后面还要加上窗口内元素个数*w,
while(hh<=tt&& g[q[tt]]-(q[tt]-r)/v*w <= g[k]-(k-r)/v*w) tt--;
//单调队列弹出不合法元素(要求队列递减)
q[++tt]=k;
}
}
}
cout<<f[m]<<endl;
return 0;
}