多重背包问题

这篇博客探讨了多重背包问题的动态规划解决方案,并详细介绍了如何利用单调队列优化来提升效率,从O(nmk)降低到更优的时间复杂度。通过分析更新规则和队列维护方式,阐述了如何在保持单调性的前提下,有效地处理多重背包问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多重背包问题

声明原题:

有n种物品,第i种物品的重量为wi,价值为vi,共ti个。
现在有一个载重量为W的背包,在不超重的情况下,使放入背包的物品价值总和最大。
求最大的价值总和。

纯DP

Fi,j表示拿完前i种物品,受重为j的最大获益。

显然Fi,j=Max{Fi1,jkwi+kvi}      (0<=k<=Min{ti,jwi})

然而这时O(nmk)的,当n,m太大时他就不行了。

那我们可以想一想,是否可以将k转化为1。我们就立刻想到了“单调”这个词,想到了单调这个词,就想到了单调队列优化dp。

单调队列也只是我前几天才学会的,单调队列它是一个队列,它具有单调性,也就是对于它的每一次询问可以O(1)完成,单调队列由两个指针头、尾指针来维护,头指针是来确保正确性和有效性的,从头指针到尾指针我们要确保这些值有效并且前者比后者优,这样我们就可以直接对头指针的值求出答案。

那么如何才能让单调队列来优化这一个多重背包问题呢?我们首先观察一下方程:

Fi,j=Max{Fi1,jWik+Vik}

我们可以通过队列存储Fi1,jWikjWik 这两个值,来维护Max{Fi1,jWik+Vik}

观察式子,我们是由Fi1,jkWi 来更新Fi,j 的值,假设我们由Fi1,l 来更新Fi,j 的值,明显Wi|(jl) ,就有jl (mod Wi),假如j和l模Wi的值不同,Fi1,l 就不可能更新到Fi,j,我们设立Wi个队列,编号为0到Wi-1,第t个队列存j mod Wi=t 的Fi1,j 和j,这样就可以确保同一编号队列里的量都可以更新到同一个值。我们也可以将同样余数的j放到一起进行运算,算完再算令一种余数的,这样就可以节省更多空间。我们设单调队列queue[1..载重上限,1..2]1表示当时的重量,2表示当时的价值。

如何才能确保一个队列始终单调呢?

对于Fi1,k可以更新Fi,j ,就有j-Wi * Ti<=k<=j-Wi,反之,只有j-Wi * Ti<=k<=j-Wi,Fi1,k才可以更新Fi,j,也就是说,当k < j-Wi * Ti,k就过期了,自然Fi1,k对更新Fi,j不起作用,我们就不需要它了。
当我们取数时,我们要判断队首是否过期,也就是这里是否已经更新不到f[i,j]了,即j-queue[h,1]>Ti*Wi这样的话这个队首就没有实际用处了,退掉,也就是h自加1,然后再判断一下新的队首:

while(h<=t)and(j-queue[h,1]>W[i]*T[i])do inc(h);

队首处理好后,我们就可以取数了,由队首的重量queue[h,1]可以得出,为了使它更新到F[i,j]应该使用jqueue[h,1]Wi个这样的背包,它们的获益就是queue[h,2]+Vijqueue[h,1]Wi

所以取数这个过程就是:

procedure get(i,j,k:longint);
begin
    while(h<=t)and(j-queue[h,1]>W[i]*T[i])do inc(h);
    if h>t then exit;
    f[i,j]:=max(f[i,j],queue[h,2]+V[i]*((j-queue[h,1])div W[i]));
end;

取完数后,我们要将f[i-1,j]这个值放入队列,因为这个值可能会更新到f[i,j+W[i]]以及后面的东西,再存入的时候,要判断新元素是否比队尾元素优,是的话就删掉队尾那个元素,也就是t自减1,并判断下一个对位元素,如果不比它优的话,就放入新的队尾。判断方法是若queue[t,2]+Vijqueue[t,1]Wi<=f[i1,j]就说明队尾元素没有新元素优,那么当更新f[i,j+k*W[i]]的时候队尾元素是否会比队首元素优呢?显然不会:

由队尾元素能更新到F[i,j+k*W[i]]的获益为

queue[t,2]+Vijqueue[t,1]Wi+Vik

由新元素能更新到F[i,j+k*W[i]]的获益为
f[i1,j]+Vik

因为
queue[t,2]+Vijqueue[t,1]Wi<=f[i1,j]

所以
queue[t,2]+Vijqueue[t,1]Wi+Vik<=f[i1,j]+Vik

由此得当队尾元素没有新元素优是,那么对于更新f[i,j+k*W[i]],队尾元素不会比队首元素优。

覆盖旧元素的过程如下:

procedure put(i,j:longint);
begin
    while(h<=t)and((queue[t,2]+((j-queue[t,1]) div W[i])*V[i])<=f[i,j])do dec(t);
    inc(t);
    queue[t,1]:=j;
    queue[t,2]:=f[i,j];
end;

于是多重背包的程序如下:

var
    f:array[0..100,0..10010]of longint;
    T,W,V:array[0..100]of longint;
    queue:array[1..10010,1..2]of longint;
    i,j,k,l,n,m,ans,head,tail:longint;

function max(x,y:longint):longint;
begin
    if x>y then exit(x) else exit(y);
end;

procedure get(i,j:longint);
begin
    while(head<=tail)and(j-queue[head,1]>T[i]*W[i])do inc(head);
    if head>tail then exit;
    f[i,j]:=max(f[i,j],queue[head,2]+V[i]*((j-queue[head,1])div W[i]));
end;

procedure put(i,j:longint);
begin
    while(head<=tail)and((queue[tail,2]+((j-queue[tail,1]) div W[i])*V[i])<=f[i-1,j])do dec(tail);
    inc(tail);
    queue[tail,1]:=j;
    queue[tail,2]:=f[i-1,j];
end;

begin
    read(n,m);
    for i:=1 to n do read(W[i],V[i],T[i]);
    fillchar(f,sizeof(f),$ff);
    f[0,0]:=0;
    for i:=1 to n do
    begin
        for k:=0 to W[i]-1 do
        begin
            head:=1;tail:=0;
            for l:=0 to (m-k)div W[i] do
            begin
                j:=l*W[i]+k;
                f[i,j]:=f[i-1,j];
                if j>=W[i] then get(i,j);
                if f[i,j]>ans then ans:=f[i,j];
                if f[i-1,j]>=0 then put(i,j);
            end;
        end;
    end;
    writeln(ans);
end.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值