多重背包单调队列优化

单调队列优化多重背包

算法分析

以一维为例

\(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 写得很烂就是了

posted @ 2021-03-07 10:56  Ing1024  阅读(108)  评论(0)    收藏  举报