多重背包单调队列优化
单调队列优化多重背包
算法分析
以一维为例
\(n\)个物品,背包容量为\(m\)
\(w_i\) 为体积 \(v_i\) 为收益 \(c_i\) 为限制数量
方程
$ f_i=max(f_{i-pw_i}+pv_i), (i/w_i<p<c_i) $

观察上图,对于i这个物品,所有橙色的部分只会由橙色的部分转移过来,并且也只能转移到橙色的部分。
所以,对于一种物品 i 可以按照对 \(w_i\) 的取模结果分成 \(w_i\) \((w_i \in [0,w_i-1])\) 族,同族之间相互转移,不同族互不影响。
考虑余数 \(u \in [0,w_i-1]\) ,对所有余数分组
对于当前余数,枚举选择此物品的个数\(p\in[0,(m-u)/w_i]\)由于物品i最多只能取\(c_i\)个,则有方程
\(f_{u+p*w_i}=max(f_{u+k*w_i}+(p-k)*v_i)\) \(,(p-c_i<k<p-1)\)
化简一下柿子
\(f_{u+p*w_i}=max(f_{u+k*w_i}-k*v_i)+p*v_i\) \(,(p-c_i<k<p-1)\)
因为是一维的,所以我们的p一定是倒序枚举的,那么\(p-c_i\)递减,\(p-1\)也是递减的,显然\(max\)里的东西可以用单调队列维护。 那么方程的转移就是线性的了。
复杂度分析
外层枚举\(n\)个物品 \(O( n )\)
内层两个循环
其实仔细思考就会发现,这两个循环本质上是遍历了一遍\(f\)数组,单调队列是线性的,所以这里的复杂度是 \(O( m )\)
总复杂度是 \(O (nm)\)
比 朴素解法\(O(m\sum\limits_{i=1}^nc_i)\) 和
二进制优化 \(O(nmlog_2c)\) 都要优秀
例题及代码
上述过程并不太好理解,可以结合模板代码进行理解
单调队列优化代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define int long long
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define _rep(i,x,y) for(int i=x;i>=y;i--)
#define N 40010
const int inf=1e18;
inline int read()
{
int num=0,fu=1; char c=getchar();
while(c!='-'&&(c<'0'||c>'9')) c=getchar();
if(c=='-') fu=-1,c=getchar();
while(c>='0'&&c<='9') num=(num<<3)+(num<<1)+(c^48),c=getchar();
return fu*num;
}
int n,m,v[N],w[N],c[N],q[N],f[N],h,t;
signed main()
{
n=read(); m=read();
rep(i,1,n) v[i]=read(),w[i]=read(),c[i]=read();
rep(i,1,n)
{
rep(u,0,w[i]-1)
{
h=1; t=0;//新建单调队列
int maxp=(m-u)/w[i];//p的最大值
rep(p,max(maxp-c[i],0*1ll),maxp-1)
{
while(h<=t&&f[u+p*w[i]]-p*v[i]>=f[u+q[t]*w[i]]-q[t]*v[i]) t--;
q[++t]=p;
}//提前将第一批决策点加入单调队列
_rep(p,maxp,0*1ll)
{
while(h<=t&&q[h]>=p) h++; //排除过时决策
if(h<=t) f[u+p*w[i]]=max(f[u+p*w[i]],f[u+q[h]*w[i]]-q[h]*v[i]+p*v[i]);//转移,因为有多个物品,所以这里取max
if(p-c[i]-1>=0) //加入新决策
{
while(h<=t&&(f[u+(p-c[i]-1)*w[i]]-(p-c[i]-1)*v[i]>=f[u+q[t]*w[i]]-q[t]*v[i])) t--;
q[++t]=p-c[i]-1;
}
}
}
}
int ans=-inf;
rep(i,0,m) ans=max(ans,f[i]);
cout<<ans<<endl;
return 0;
}
闲话
其实这题二进制优化也能过
关于二进制优化,参考我的这篇blog 写得很烂就是了

浙公网安备 33010602011771号