[NDK 优化多重背包]

单调队列优化多重背包(转自:http://hi.baidu.com/lydrainbowcat/blog/item/d9fb791fc48ae1e0e0fe0bab.html

   多重背包仅涉及体积而不涉及价值的一类可以使用染色在O(nv)时间、20行左右秒过,然而对于涉及价值的多重背包,则要使用二进制拆分物品优化,或者使用效率更高应用更广的单调队列(O(nv))。

   若有n种物品,背包容量为m,物品体积、价值、最大使用次数为v,w,c,则朴素的动规方程为:f[i]=max{f[i-v*k]+w*k} (1<=k<=c)。我们把所有可能达到的体积按照除以当前物品体积v的余数划分为0~v-1,则当余数为k(k∈[0,v-1])时又可以划分为k,k+v,k+2*v…k+j*v…(1<=j<=(m-k)div v)这几种具体的体积,由于对于余数为k时的转移只会发生在以上列举出的几个体积上,所以可以建立关于以上几个体积的单调队列,以便于快速地找到最优决策。但是这里要注意一点,由于这几个决策的体积和价值都不相同,直接没有可比性,所以我们把这些决策的体积统一到为k时比较,统一方法只需要利用体积为k+j*v这一特点,把需要插入队中的f[k+j*v]的价值减去j*w,就是当体积为k时的一个可以用于比较的“参考”价值。可以很容易想到:由于转移时,使用当前物品贡献的那一部分是二者之差,所以这与减掉j*w之前是等效的。

   这样,每次求f[k+j*v]时,只需要在队列中找到一个最优的决策f[k+j’*v],使得j-j’<=c即可,剩下的工作就只有维护单调队列了。

核心代码和注释:

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,m);  //n
为物品个数、m为背包容量
 for i:=1 to n do
  begin
    read(v,w,c);  //
读入当前物品:v为物品体积、w为物品价值、c为物品可用次数

    if m div v<c then c:=m div v;  //最多可使用次数
    for k:=0 to v-1 do  //
把所有的体积按除以v的余数划分为0~v-1
     begin
       l:=1;r:=0;  //
清空队列
       for j:=0 to (m-k) div v do  //
余数为k的又分为k,v+k,2*v+kj*v+k
         begin
          insert(j,f[j*v+k]-j*w);  //
等效于把体积统一到k,价值减去j*w,这样比较优劣才有意义
         while a[l]<j-c do inc(l);  //
删除次数超过c
          f[j*v+k]:=b[l]+j*w;      //
用队列头的值更新f[j*v+k]
         end;
     end;
  end;
  writeln(f[m]);

end.


二进制优化多重背包(祥见《背包9讲》)

View Code
 1 program project1;
2 var
3 n, m: longint;
4 v: array[0..2000] of int64;
5 f: array[0..200000] of boolean;
6 s: array[0..300] of longint;
7
8 procedure init;
9 var
10 i, j, tot, x, y, temp: longint;
11 begin
12 read(n,m);
13 tot := 0;
14 for i := 1 to m do
15 begin
16 read(x,y);
17 temp := 0;
18 while x >= s[temp] do
19 begin
20 inc(tot);
21 v[tot] := s[temp]*y;
22 dec(x,s[temp]);
23 inc(temp);
24 end;
25 if x >= 0 then
26 begin
27 inc(tot);
28 v[tot] := y*x;
29 end;
30 end;
31 readln;
32 m := tot;
33 //writeln(m);
34 //for i := 1 to m do write(v[i],'');
35 //writeln;
36 end;
37
38 procedure work;
39 var
40 i, j, ans: longint;
41 begin
42 fillchar(f,sizeof(f),false);
43 f[0] := true;
44 for i := 1 to m do
45 for j := n downto v[i] do
46 f[j] := f[j] or f[j-v[i]];
47 for i := n downto 0 do
48 if f[i] then
49 begin
50 writeln(i);
51 break;
52 end;
53 end;
54
55 procedure done;
56 var
57 i: longint;
58 begin
59 s[0] := 1;
60 for i := 1 to 30 do
61 s[i] := s[i-1]*2;
62 end;
63
64 begin
65 done;
66 while not seekeof() do
67 begin
68 init;
69 work;
70 end;
71 end.

你可能感兴趣的:(NDK)