java多重背包算法,[z]多重背包中多次背包 O(VN)算法1 (单调队列优化)

多次背包

多次背包问题:给定 n 种物品和一个背包。第 i 种物品 的价值是 Wi ,其体积

为 Vi,数量是 Ki件,背包的容量为 C。可以任意选择装入背包中的物品,求装入背

包中物品的最大总价值。

方法一:可以把此物品拆分成Ki个只能用一次的物品,直接套用 0-1 背包问题的经典动规实现,但是效率太低了,需要寻找更高效的算法。此算法时间复杂度为O(C*∑(Ki))

方法二:拆分成体积和价值分别为原来1, 2 , 4..   2^m,    Ki-2^m 倍的几个物品,用0-1背包求解。 时间复杂度为O(C*∑([log2Ki]))

方法三(本文重点):(对单调队列没有了解的请参见原论文[本文结尾链接])对于第 i 种物品来说,已知体积 v,价值 w,数量 k,那么可以按照当前枚举的体积 j 对v的余数把整个动规数组分成 v份,以下是 v=3 的情况:

j             0 1 2 3 4 5 6 7 8 ……

j mod v   0 1 2 0 1 2 0 1 2 ……

我们可以把每一份分开处理,假设余数为 d。

编号j         0     1        2            3           4             5         ……

对应体积 d    d+v    d+2*v     d+3*v    d+4*v      d+5*v    ……

现在看到分组以后,编号 j 可以从 j-k 到 j-1 中的任意一个编号转移而来(因为相邻的体积正好相差 v) ,这看上去已经和区间最大值有点相似了。但是注意到由于体积不一样,显然体积大的价值也会大于等于体积小的,直接比较是没有意义的,所以还需要把价值修正到同一体积的基础上。比如都退化到 d,也就是说用 F[j*v+d]- j*w来代替原来的价值进入队列。

对于物品i,伪代码如下

1. FOR d: = 0 TO v-1                     //枚举余数,分开处理

2.   清空队列

3.   FOR j: = 0 TO (C-d) div v           //j 枚举标号,对应体积为 j*v+d

4.    INSERT j , F[ j*v+d ] – j * w      //插入队列

5.    IF A[ L ] < j - k THEN L + 1 → L //如果队列的首元素已经失效

6.    B[ L ] + j * w → F[ j*v+d ]       //取队列头更新

7.   END FOR

8. END FOR

已知单调队列的效率是 O(n),那么加上单调队列优化以后的多次背包,

效率就是 O(n*C)了。

(详细请参见原论文)

==========================================================

完整程序如下(Pascal):

var

a,b,f:array[0..100000] of longint;

m,s,c,n,t,i,j,l,r,d:longint;

procedure insert(x,y:longint);

begin

while (l<=r)and(b[r]<=y) do dec(r);

inc(r);a[r]:=x;b[r]:=y;

end;

begin

readln(n,t);              //读入数据 n为物品个数 t为背包容量

for i:=1 to n do

begin

read(m,s,c);         //读入当前物品 m为物品体积、s为物品价值、c为物品可用次数(0表示无限制)

if (c=0)or(t div m

for d:=0 to m-1 do

begin

l:=1;r:=0;     //清空队列

for j:=0 to (t-d) div m do

begin

insert(j,f[j*m+d]-j*s);   //将新的点插入队列

if a[l]

f[j*m+d]:=b[l]+j*s;        //用队列头的值更新f[j*m+d]

end;

end;

end;

writeln(f[t]);

end.

==========================================================

本文内容参考国家集训队2009论文 徐持衡《浅谈几类背包题》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值