多重背包问题:
有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 }
codevs 3269 混合背包