题目描述:
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
基本算法:
这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取 n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则:f[i][v]=max{f[i-1][v-k*c[i]]+ k*w[i]|0<=k<=n[i]}。复杂度是O(V*∑n[i])。
转化为01背包问题
另一种好想好写的基本方法是转化为01背包求解:把第i种物品换成n[i]件01背包中的物品,则得到了物品数为∑n[i]的01背包问题,直接求解,复杂度仍然是O(V*∑n[i])。但是我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。仍然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略——取0..n[i]件——均能等价于取若干件代换以后的物品。另外,取超过n[i]件的策略必不能出现。
方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为 1,2,4,...,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示。
但是,有时候单纯只转化为01背包问题,还是会超时,这时我们可以先用完全背包,在使用二进制转化后的01背包。先用完全背包做的前提是:某一种物品的总体积大于容器的体积,那么这时就相当于物品可以无限的取用,因为容器体积满了时,物品总是没有用光的。
看具体题目:
1、HD2844
题意:给出硬币的价值以及该价值硬币的数量还有m,计算在1到m之间的用给定的硬币可以拼凑出的钱的数目。在这个题中,硬币的价值看做物品的体积。
代码:
#include <iostream> #include<memory.h> using namespace std; int n,m,A[101],C[101],vol[101],V[100010]; int main() { int f,count; while(cin>>n>>m&&n!=0&&m!=0) { count=0; for(int i=0;i<n;i++) cin>>A[i]; for(int i=0;i<n;i++) cin>>C[i]; memset(V,0,sizeof(V)); for(int i=0;i<n;i++) { if(A[i]*C[i]>m)//用完全背包做 { for(int j=A[i];j<=m;j++) if(V[j]<V[j-A[i]]+A[i]) V[j]=V[j-A[i]]+A[i]; } else { f=1; int temp; while(C[i]>f) { temp=A[i]*f; for(int j=m;j>=temp;j--) if(V[j]<V[j-temp]+temp) V[j]=V[j-temp]+temp; C[i]-=f; f<<=1; } temp=A[i]*C[i]; for(int j=m;j>=temp;j--) if(V[j]<V[j-temp]+temp) V[j]=V[j-temp]+temp; } } for(int i=1;i<=m;i++) if(V[i]==i) count++; cout<<count<<endl; } }
这个题中,大米价格以及经费的金额看作是容器的体积,而大米的重量看作是物品的价值,根据题意是要求所能得到的大米的最大的重量。
代码:
#include <iostream> #include<memory.h> using namespace std; struct Rice { int p,h; }; Rice r[501]; int M[20010]; int main() { int C,n,m,a,b,c,f; cin>>C; for(int i=0;i<C;i++) { cin>>n>>m; memset(M,0,sizeof(M)); for(int j=0;j<m;j++) { cin>>a>>b>>c; if(a*c>n)//用完全背包 { for(int k=a;k<=n;k++) if(M[k]<M[k-a]+b) M[k]=M[k-a]+b; } else{ f=1; while(c>f) { for(int k=n;k>=a*f;k--) if(M[k]<M[k-a*f]+b*f) M[k]=M[k-a*f]+b*f; c-=f; f<<=1; } for(int k=n;k>=a*c;k--) if(M[k]<M[k-a*c]+b*c) M[k]=M[k-a*c]+b*c; } } cout<<M[n]<<endl; } }
题意:给出1-6每个数字的个数,然后给出的数字能否被分成两部分,使这两部分的和相等。这个题目中数字的价值可看作是背包的体积。
#include <iostream> #include<memory.h> using namespace std; int num[7],V[430000],a=0,vol[100]; int main() { int sum,f; bool judge; while(true) { a++; judge=false; sum=0; for(int i=1;i<7;i++) { cin>>num[i]; if(num[i]!=0) { sum+=i*num[i]; judge=true; } } if(!judge) break; cout<<"Collection #"<<a<<":\n"; if(sum%2){ cout<<"Can't be divided.\n\n"; continue; } sum/=2; memset(V,0,sizeof(V)); for(int i=1;i<7;i++) { if(i*num[i]>sum)//完全背包 { for(int j=i;j<=sum;j++) if(V[j]<V[j-i]+i) V[j]=V[j-i]+i; } else{ f=1; while(num[i]>f) { for(int j=sum;j>=i*f;j--) if(V[j]<V[j-i*f]+i*f) V[j]=V[j-i*f]+i*f; num[i]-=f; f<<=1; } for(int j=sum;j>=num[i]*i;j--) if(V[j]<V[j-num[i]*i]+num[i]*i) V[j]=V[j-num[i]*i]+num[i]*i; } } if(V[sum]!=sum) cout<<"Can't be divided.\n\n"; else cout<<"Can be divided.\n\n"; } }