多重背包之单调队列优化

多重背包之单调队列优化

模板题

简单回顾在以前的多重背包中是如何求解的:

第一种把多重背包当作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[jv,m]dp[i][k]=max(dp[i1][k],dp[i1][kjv]+jw)

第二种多重背包是将每个物品的个数进行二进制拆分,使得任意一种物品的个数都可以通过若干的拆分后的二进制表示,此时可以当作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[i1][j],dp[i1][jv]+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]+xw

参考代码:

#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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值