O(V*n)的多重背包问题

多重背包问题:

  有n件物品,第i件价值为wi,质量为vi,有c1件,问,给定容量V,求获得的最大价值。

  

朴素做法:

  视为0,1,2,...,k种物品的分组背包 [每组只能选一个]

  f[i][j]=Max(f[i][j-k*v[i]]+k*w[i])

  但是i,j,k都要枚举,复杂度为 n*V*k

 

朴素做法的改进:

  因为发现用二进制可以表示1..k之内的所有数 [整数二进制打开后为01串,所以可以被二进制表示]

  所以将k个物品拆分成1,2,4...2^m,k-2^m   ( 其中2^m<=k<2^(m+1) ) 这些物品,然后变成01背包问题。

  但是n的数目增多了,复杂度为 n*V*logk

 

利用单调队列的改进:

  1.我们可以发现每个容量都能表示成 v*x+d 的形式[ v表示当前考虑的物品的容量 ]

  2.在上一点的启发下,我们发现一个f[v*x+d]在考虑当前物品时,只能由f[v*y+d]转移而来。 [其中x-y<=k]。

  也就是说,对v取模的余数相同的容量之间才能互相转移,而且要求x-y<=k。又因为求的是最大值的转移,所以满足单调队列的适用性。

  于是乎,我们对于余数d相同的容量分别建一个单调队列,然后枚举x f[x*v+d],进行转移即可。

  

 1 #include<cstdio>
 2 #include<cstring>
 3 
 4 inline int in(){
 5     int x=0,flag=1;char ch=getchar();
 6     while(ch!='-' && (ch>'9' || ch<'0')) ch=getchar();
 7     if(ch=='-') flag=-1,ch=getchar();
 8     while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
 9     return x*flag;
10 }
11 
12 int a[200005],b[200005],f[200005];
13 int w,v,k,n,V,l,r;
14 
15 void insert(int x,int y){
16     while(l<=r && b[r]<=y) r--;
17     a[++r]=x; b[r]=y;
18 }
19 
20 inline int Max(int a,int b){
21     if(a>b) return a;return b;
22 }
23 
24 int main(){
25     n=in(),V=in();
26     int Lim;
27     for(int i=1;i<=n;i++){
28         v=in();w=in();k=in();
29         if(k==1){
30             for(int j=V;j>=v;j--)
31                 f[j]=Max(f[j],f[j-v]+w);
32             continue;
33         }
34         else if(k<0){
35             for(int j=v;j<=V;j++)
36                 f[j]=Max(f[j],f[j-v]+w);
37             continue;
38         }
39         if(V/v<k) k=V/v;
40         for(int d=0;d<v;d++){
41             l=1,r=0;Lim=(V-d)/v; 
42             for(int x=0;x<=Lim;x++){
43                 insert(x,f[x*v+d]-x*w);
44                 if(a[l]<x-k) l++;
45                 f[x*v+d]=b[l]+x*w;
46             }
47         }
48     }
49     printf("%d",f[V]);
50     return 0;
51 }
View Code

codevs 3269 混合背包

AC通道:http://codevs.cn/problem/3269/

posted @ 2015-10-02 17:13  诚叙  阅读(537)  评论(0编辑  收藏  举报