多重背包之单调队列优化
简单回顾在以前的多重背包中是如何求解的:
第一种把多重背包当作01背包,时间复杂度O(nml)。假设物品个数为n,背包体积m,单个物品体积v,价值w,个数l
f o r i f r o m [ 1 , n ] f o r j f r o m [ 1 , l ] f o r k f r o m [ j ∗ v , m ] d p [ i ] [ k ] = m a x ( d p [ i − 1 ] [ k ] , d p [ i − 1 ] [ k − j ∗ v ] + j ∗ w ) for \quad i \quad from \quad [1, n] \\\quad for \quad j \quad from \quad [1, l] \\\quad \quad \quad \quad for \quad k \quad from \quad [j * v, m] \\ dp[i][k] = max(dp[i - 1][k], dp[i - 1][k - j * v] + j * w) forifrom[1,n]forjfrom[1,l]forkfrom[j∗v,m]dp[i][k]=max(dp[i−1][k],dp[i−1][k−j∗v]+j∗w)
第二种多重背包是将每个物品的个数进行二进制拆分,使得任意一种物品的个数都可以通过若干的拆分后的二进制表示,此时可以当作01背包进行处理,时间复杂度O(nmlogl)。假设二进制拆分后物品总个数为n,背包体积m,单个物品体积v,价值w
f o r i f r o m [ 1 , n ] f o r j f r o m [ v , m ] d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v ] + w ) for \quad i \quad from \quad [1, n] \\ \quad for \quad j \quad from \quad [v, m] \\ dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v] + w) forifrom[1,n]forjfrom[v,m]dp[i][j]=max(dp[i−1][j],dp[i−1][j−v]+w)
第三种就是本节重点单调队列优化多重背包,以一个特别优秀的时间复杂度O(nm)胜出。为什么想到会用单调队列优化呢?先来回想一下,单调队列可以维护的是连续的区间内区间最值,在进行多重背包的时候如何使用呢?首先,我们先观察多重背包的转移,不难看出的是最外层的for循环都是循环的每种物品,并且这个物品的体积是确定的,不确定的是这个物品的选取数量,所以单次转移所包含的,在模v的意义下一定是相同的那些下标,所以我们将转移的下标进行模v运算,所以所有的转移起点都包含在[0,v)区间内,比如v=4,当k=1时能转移到的只有k=5,k=9,k=13···但现在依然还没有用到单调队列去维护。假设当前的下标为k,那他所能转移到的就是[k+v,k+l*v],现在把这些下表进行重新标号,那就是下标为k,能转移到的就是[k+1,k+l],假如转移到x,f[x]=max(f[k]+(x-k)*w),把带k的分离出来就是,f[x]=max((f[k]-k*w)+x*w),此时发现x*w是一个固定值,而不固定的f[k]-w*k就是单调队列里存储的对象,那么维护的区间长度就是l的长度。假设物品个数为n,背包体积m,单个物品体积v,价值w,个数l,q[][]为单调队列,q[][0]记录的是值,q[][1]记录的是下标,head为单调队列首部,tail为单调队列尾部,e为f[j]-x*w,此处的j不是体积在模v下的值,而是表示从下标为j的地方向下标为x的地方转移的前驱,r为当前下标的上边界,x为当前需要更新的下标。
f o r i f r o m [ 1 , n ] f o r j f r o m [ 0 , v ) f o r p f r o m [ j , m ] d p [ x ] = q [ h e a d ] [ 0 ] + x ∗ w for \quad i \quad from \quad [1, n] \\ \quad for \quad j \quad from \quad [0, v) \\ \quad \quad for \quad p \quad from \quad [j, m] \\ \quad \quad \quad \quad dp[x] = q[head][0] + x * w forifrom[1,n]forjfrom[0,v)forpfrom[j,m]dp[x]=q[head][0]+x∗w
参考代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int q[100010][2], dp[100010];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int v, w, l;
cin >> v >> w >> l;
for (int j = 0; j < v; j++) {
int head = 1, tail = 0;
for (int p = j, x = 1; p <= m; x++, p += v) {
int val = dp[p] - w * x;
int r = x + l;
for (; head <= tail && q[tail][0] <= val; tail--);
q[++tail][0] = val;
q[tail][1] = r;
dp[p] = q[head][0] + w * x;
for (; head <= tail && q[head][1] == x; head++);
}
}
}
cout << dp[m] << '\n';
return 0;
}