题目背景
本题考查多重背包的二进制优化方法。
题目描述
有 N 种物品和一个容量是 V 的背包。
第 i 种物品每件体积是 vi,价值是 wi,最多有 si 件。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。 输出最大价值
输入格式
第一行两个整数,N,V用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si 用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
输入 #1
4 5 1 2 3 2 4 1 3 4 3 4 5 2
输出 #1
10
说明/提示
数据范围
0<N≤2000
0<V≤4000
0<vi,wi,si≤2000
此题来源于洛谷U299430
做这道题,我们需要用到01背包降维,完全背包降维,多重背包降维三个知识点,在这里简单复习一下
首先是01背包
顾名思义,有两种取法,取或不去
经典例题:洛谷P1048 [NOIP2005 普及组] 采药
for (int i = 1; i <= n; i++) {
for (int j = m; j >= w[i]; j--) {
if (w[i] > j) f[j]=f[j];
else {
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
}
}
完全背包
每个物品有无限个,可以取多个
经典例题:洛谷U200606 完全背包问题
for (int i = 1; i <= n; i++) {
for (int j = w[i]; j <= m; j++) {
if (w[i] > j) f[j]=f[j];
else {
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
}
}
多重背包
一个物品最多能取s个
经典例题:洛谷U200694 庆功会
for(int i=1;i<=n;i++){
for(int j=m;j>=1;j--){
for(int k=0;k<=s[i];k++){
if(j-k*v[i]>=0) f[j]=max(f[j-k*v[i]]+k*w[i],f[j]);
}
}
}
然后我们进入正题
其实我们可以把他转化为01背包问题,不会?且听我慢慢道来
【转化为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]间的每一个整 数,均可以用若干个系数的和表示,这个证明可以分 0..2^k-1和2^k..n[i]两段来分别讨论得出,并不难,希望你 自己思考尝试一下。 这样就将第i种物品分成了O(log n[i])种物品,将原问题转 化为了复杂度为O(V*Σlog n[i])的01背包问题,是很大的改进。
下面给出O(log amount)时间处理一件多重背包中物品的过程,其中amount表示物品的数量:void ZeroOnePack(int cost,int wei
理论成立,那么实践,他来了!!!
void ZeroOnePack(int cost,int weight){
for(int i=m;i>=cost;i--){
f[i]=max(f[i],f[i-cost]+weight);
}
}
void CompletePack(int cost,int weight){
for(int i=cost;i<=m;i++){
f[i]=max(f[i],f[i-cost]+weight);
}
}
void MultiplePack(int cost,int weight,int amount){
if(cost*amount>=m){
CompletePack(cost,weight);
return;
}//转为完全背包[件数太多,可视为无穷]
int k=1;//折分的思想
while(k<amount){
ZeroOnePack(k*cost,k*weight);//转为01背包
amount=amount-k;
k=k*2;
}
ZeroOnePack(amount*cost,amount*weight);//转为01背包
}
(此代码仅供学习参考,严禁抄袭)