单调队列优化多重背包(全网最详细解析)

前置知识

多重背包 (背包九讲)
背包九讲——全篇详细理解与代码实现_良月澪二的博客-CSDN博客_背包九讲
 

单调队列

单调队列详解_Jiandong-CSDN博客_单调队列详解

前言

笔者学习这一算法耗了很多精力,网上多重背包讲解多如牛毛,单调队列优化讲的也不少,但大都是蜻蜓点水罢了,很不详细。在此决定自己亲自写一篇,也是对知识点的梳理。

首先,对于常规多重背包,f[i][j]代表体积为j,选到第i个物品的最大价值。由于一种物体数量可以多个,故需要枚举数量。

状态转移方程如下

f[i][j]=max( f[i-1] [j-k*v[i]] +k*w[i] ) 其中0<=k<=cnt[i]

cnt[i]代表这一种物体数目,w[i]是价值(wealth), v[i]是体积

我们设  j=k1*v[i] +d       k1=j/v[i]  d=j%v[i]   前者是j能装下的最大个数,后者是余数

下面建议拿本子推一下

f[i][j]= max( f[i-1][ k1*v[i]+d -k*v[i]] + k*w[i] )   0<=k<=cnt[i]

没做太多变化,把j代换成了  k1*w[i] +d

接下来,合并同类项

f[i][j]= max( f[i-1][ (k1-k)* v[i]+d] -(k1 -k) *w[i] +k1*w[i] )  0<=k<=cnt[i]

把k和k1提取出来了,另外把 k*w[i] 改写成 (k1 -k) *w[i] +k1*w[i] 

f[i][j]= max( f[i-1][ (k1-k)* v[i]+d] -(k1 -k) *w[i] ) + k1*w[i]  0<=k<=cnt[i]

对于确定的d,j,k1是常数,故提取出外面

为了更直观看出这样转化的原理,看下面例子

f[i][ 6v+d ] =  max( f[i-1][6v+d] ,f[i-1][5v+d] + w ,f[i-1][4v+d] +2w , f[i-1][3v+d] +3w.....)

应该没什么毛病,对于确定的j=6v+d,往里面塞0个,1个,2个..的价值

然后,我们对max括号里每一项,全部减去 6w ,6指的是当前j能装下的最多数目

f[i][ 6v+d ] =  max( f[i-1][6v+d]-6w ,f[i-1][5v+d] -5w  ,f[i-1][4v+d] -4w , f[i-1][3v+d] -3w....) +6*w

同时减去相同数字,大小关系不变。

我们惊奇发现,这不就是上面的式子吗?k1=6 ,(k1-k)分别是那些5,4,3,那些系数

f[i][j]= max( f[i-1][ (k1-k)* v[i]+d] -(k1 -k) *w[i] ) + k1*w[i]  0<=k<=cnt[i]

我们继续来看下面这个式子

f[i][ 6v+d ] =  max( f[i-1][6v+d]-6w ,f[i-1][5v+d] -5w  ,f[i-1][4v+d] -4w , f[i-1][3v+d] -3w....) +6*w

它就告诉了我们,想知道左边的,必须求出右边括号里的,然后再加上6*w,你可能会说,这不显然吗?

哎别,如果我们一个一个·枚举,效率不知道会低多少,这就引出了我们的杀手锏--单调队列

它的精华在于维护一个区间(窗口)的一个最值。

那么这个公式和窗口有什么关系呢?

嘿嘿,看清楚了,别眨眼,我们把(k1-k)用k,代换 ,修改k的定义域

原来是0<=k<=cnt[i]   ,现在  k1-cnt[i]<= k <= k1   。这时候我们惊奇的发现,当j变化时,k1随之变化,k值也随之变化,但k值始终在一个窗口里!! 

所以,我们对一个j,用一个单调队列维护窗口内的一些值,那些值?

接着看那个式子

f[i][j]= max( f[i-1][ (k1-k)* v[i]+d] -(k1 -k) *w[i] ) + k1*w[i]  0<=k<=cnt[i]

(k1-k)-->k

f[i][j]= max( f[i-1][ k* v[i]+d] -k *w[i] ) + k1*w[i]  k1-cnt[i]<= k <=k1

我们维护窗口内, f[i-1][k*v[i]+d]  -k*w[i] 

到达我们滑动到目标j,的时候,区间最值已经出来了,f[i][j]就等于那个最值 + k1*w[i]

大功告成!!  

但我们还不满足,我们不满足于二维,我们把它滚成一维,如果对这方面还不熟悉,可以参考背包九讲。

首先,对于新输入的一个物品,w,v,c代表价值,体积,数目,已知最大体积V,那么它最多能容纳V/v 个,

int v,w,c;
cin>>v>>w>>c;

int k=V/v;
c=min(c,k);

c可能大于最大容量,所以取最小值.

我们枚举余数d, 对于每个体积,它模v,余数在0---v-1范围内

for(int d=0;d

然后对于一个确定的d,j最多能容纳  

k=(V-d)/v;

枚举k,之前别忘了初始化单调队列

每次加入单调队列的,是dp[d+j*v]-j*w  ,  这样,在计算每个目标点时,只需要提取出窗口最大值,再加上特定的k1*w即可

for(int d=0;d=q2[tail-1])
					tail--;
				q[tail]=j;
				q2[tail++]=dp[d+j*v]-j*w;
				while(head

最后输出dp[V]即可

大功告成,如果对您有利,请点个赞,关注一下俺。

自我介绍一下,目前在读中国石油大学大一,平时多更新codeforces题解,热爱编程。

你可能感兴趣的:(动态规划,算法)