多重背包

【问题描述】

        有N种物品和一个容量为w的背包,第i种物品最多有m[i]件可用,每件容量是c[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

【输入】

          第一行 正整数n和w

         以下n行,每行三个正整数为物品的容量、价值和件数,中间用空格隔开。

【输出】

最大价值总和。

【数据规模】

        1<= n<=100, w<=10000, 1<=m[i]<=10000, 1<=c[i],v[i]<=100

 分析:

方法1,朴素算法:f[i,j]表示前i种物品装入容量为j的背包中所能得到的最大价值(0<=k<=min(m[i],j div v[i]))。  

         f[i,j]=max{f[i-1,j-k*c[i]]+k*v[i]}   ,0<=k<=min(m[i],j div v[i])。   

         时间复杂度O(n*V*∑m[i])

方法2,转换为01背包,利用二进制拆分

          假设第i种物品有m[i]件,则可以第i种物品看成m[i]件01背包中的物品,则得到了物品数为Σm[i]的01背包问题,直接求解.时间复杂度:O(V*Σ m[i])。

二进制拆分:

       m[i]=1+2+4+...+2^k+a   (0<=a<2(k+1),a=n-2^(K+1)+1)

       由于1,2....,2^k可以表示0~2^(K+1)-1中的所有整数(由K位二进制表示),则1,2,4,...,2^k,a可以表示出0~m[i]的所有整数(a与0~2^(K+1)-1的所有整数相加,可以表示出2^(K+1)~n的所有整数)。

       m[i]可以分解出log(m[i])+1个背包,则时间复杂度为O(V*∑log(m[i]))

方法3,单调队列

    方法1中f[i,j]=max{f[i-1,j-k*c[i]]+k*v[i]}   ,0<=k<=min(m[i],j div v[i])。

   令a[j]=f[i-1,j*c[i]],k=m[i],假设当前重量为c[i]的整数倍,即j mod  c[i]=0 则有

   f[i,(j+k)*c[i]]=max{f[i-1,(j+k)*c[i]-k*c[i]]+k*v[i],f[i-1,(j+k)*c[i]-(k-1)*c[i]]+(k-1)*v[i],...,f[i-1,(j+k)*c[i]+(k-k)*c[i]]+(k-k)*v[i] 

                       =max{f[i-1,j*c[i]]+k*v[i],f[i-1,(j+1)*c[i]]+(k-1)*v[i],...f[i-1,(j+k)*c[i]]}

                       =max{a[j]+k*v[i],a[j+1]+(k-1)*v[i],...,a[j+k]}

这样还是不方便计算,令b[j]=a[j]-j*v[i]

    则有f[i,(j+k)*c[i]]=max{b[j]+(j+k)*v[i],b[j+1]+(J+K)*v[i],...,b[j+k]+(j+k)*v[i]}

                               =max{b[j],b[j+1],...,b[j+k]}+(j+k)*v[i]

这样就可以使用单调队列来求解了,时间复杂度O(nw)。

 (注意,前面的公式推导是假设当前重量为c[i]的整数倍,程序实现时要考虑到所有情况,如,不是c[i]整数倍情况的处理。

余数有0,1,2,......c[i]-1种情况,c[i]可以选0,1,......m[i]件)。

   for i:=1 to n do 

      for t:=0 to c[i]-1 do

           begin

            ..................

           end;

         

 1 //f[i,(j+k)*c[i]]=max{b[j],b[j+1],...,b[j+k]}+(j+k)*c[i]
 2 uses math;
 3 const
 4   maxn=110;
 5   maxw=11000;
 6 var
 7   n,w:longint;
 8   m,c,v:array[0..maxn] of longint;
 9   f:array[0..maxn,0..maxw] of longint;
10   procedure init;
11   var i:longint;
12   begin
13     assign(input,'dcb.in');reset(input );
14     readln(n,w);
15     for i:=1 to n do readln(c[i],v[i],m[i]);
16   end;
17   procedure main;
18   var
19     head,tail,x,i,j,t:longint;
20     qd,qb:array[0..maxn] of longint;
21   begin
22     for i:=1 to n do
23       for t:=0 to c[i]-1 do //枚举所有可能的余数
24         begin
25           head:=0;tail:=0;
26           fillchar(qd,sizeof(qd),0);
27           fillchar(qb,sizeof(qb),0);
28           for j:=0 to (w-t) div c[i] do //j*c[i]+t<=w;枚举余数为t,重量为j*c[i]+t时的情况
29             begin  //维护队列中元素个数<=c[i]的单调队列
30               x:=f[i-1,j*c[i]+t]-j*v[i];//计算b[j]
31               while (head<tail)and(qd[tail]<=x) do dec(tail);//保持队列的单调递减
32               inc(tail);qb[tail]:=j;qd[tail]:=x;//b[j]与下标j分别入队
33               f[i,j*c[i]+t]:=qd[head+1]+j*v[i];//队首元素(队列中最大的元素)+j*v[i],
34               if qb[head+1]=j-m[i] then inc(head);//如果正好是第j-m[i]个下标则出队,即构成0~k的循环了。
35             end;
36           end;
37   end;
38 begin
39   assign(output,'dcb2.out');rewrite(output);
40   init;
41   main;
42   writeln(f[n,w]);
43   close(output);
44 end.
View Code

(注意也可以用一维数组实现)

原文地址:https://www.cnblogs.com/ssfzmfy/p/3795293.html