动态规划——背包问题(二)

三、多重背包问题

问题:
有N种物品和一个容量为v的背包。第i种物品最多有n[i]件可用,每件费用是w[i],价值是c[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包的,且价值总和最大。
基本算法:
这个题目和完全背包问题和类似。基本的方程只需要略微改动即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容器为v的背包的最大权值,则:f[i][v]=max{f[i-1][v-k*w[i]]+k*c[i]|0<=k<=n[i]}。复杂度是O(V*∑n[i])。
例题:庆功会
【问题描述】为了庆贺班级在校运动会上取得全校第一的成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能够购买最大价值的奖品,可以补充他们的精力和体力。
【输入格式】
第1行两个数n(n<=500),m(m<=6000),其中n代表希望购买的奖品的种数,m表示拨款金额。
接下来n行,每行3个数,v、w、s分别表示第i种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到买s件均可),其中v<=100,w<=1000,s<=10。
【输出格式】
一个数,表示此次购买能获得的最大的价值(注意!不是价格)。
【输入样例】

5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1

【输出样例】

1040

代码如下:

#include<iostream>
#include<cstdio>
#include<string.h> 
#include<algorithm>
using namespace std;
int v[6001],w[6001],s[6001],f[6001],n,m;

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&v[i],&w[i],&s[i]);
    }
    for(int i=1;i<=n;i++){
        for(int j=m;j>=0;j--){
            for(int k=0;k<=s[i];k++){
                if(j-k*v[i]<0){
                    break;
                }
                f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
            }
        }
    }
    printf("%d",f[m]);
    return 0;
}

如何优化:
转化为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,且k满足n[i]-2^k+1>0的最大整数(注意:这些系数已经可以组合出1~n内的所有数字)。例如,如果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背包问题,是很大的改进。
二进制优化代码:

#include<iostream>
#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
int v[6001],w[6001],f[6001],n,m,n1,x,y,s,t=1;

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&x,&y,&s);
        while(s>=t){
            v[++n1]=x*t;
            w[n1]=y*t;
            s-=t;
            t*=2;
        }
        v[++n1]=x*s;
        w[n1]=y*s;//s2的指数分堆:1,2,4,...,2^(k-1),s-2^k+1; 
    }
    for(int i=1;i<=n1;i++){
        for(int j=m;j>=v[i];j--){
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    printf("%d",f[m]);
    return 0;
}

四、混合三种背包问题

问题:
如果将01背包,完全背包,多重背包混合起来。也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢。
01背包与完全背包的混合:
考虑到01背包和完全背包中最后给出的伪代码只有一处不同,故如果只有两类物品:一类物品只能取一次 ,另一类物品可以取无限次,那么只需要在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是O(VN)
伪代码如下:

for i=1..N
  ifi件物品是01背包
    for v=V..0
      f[v]=max{f[v],f[v-w[i]]+c[i]};
  else ifi件物品是完全背包
    for v=0..V
      f[v]=max{f[v],f[v-w[i]]+c[i]};
  再加上多重背包

如果再加上有的物品最多可以取有限次,那么原则上也可以给出O(VN)的解法:遇到多重背包类型的物品用单调队列解即可。但如果不考虑超过Noip范围的算法的话,用多重背包中将每个这类物品分为O(log n[i])个01背包的物品的方法也已经很优了。
例题、混合背包
【问题描述】
一个旅行者有一个最多能装V公斤的背包,现在有n件物品,它们的重量分别是W1,W2,…Wn,它们的价值分别为C1,C2,…,Cn。有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取得次数有一个上限(多重背包)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
【输入格式】
第1行:两个整数,M(背包容量,M<=200),N(物品数量,N<=30);
第2行至N+1行:每行三个整数Wi,Ci,Pi,前两个整数分别表示每个物品的重量,价值,第三个整数若为0,则说明此物品可以购买无数件;若为其他数字,则为此物品可购买的最多件数(Pi)。
【输出格式】
仅一行,一个数,表示最大总价值。
【输入样例】

10 3
2 1 0
3 3 1
4 5 4

【输出样例】

11

【样例说明】
选第一件物品1件和第三件物品2件。
代码如下:

#include<iostream>
#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
int w[31],c[31],p[31],f[201],m,n;

int main(){
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&w[i],&c[i],&p[i]);
    }
    for(int i=1;i<=n;i++){
        if(p[i]==0){               //完全背包问题 
            for(int j=w[i];j<=m;j++){
                f[j]=max(f[j],f[j-w[i]]+c[i]);
            }
        }
        else{                       //01背包和多重背包问题 
            for(int j=1;j<=p[i];j++){
                for(int k=m;k>=w[i];k--){
                    f[k]=max(f[k],f[k-w[i]]+c[i]);
                }
            }
        }
    }
    printf("%d",f[m]);
    return 0;
}
    原文作者:动态规划
    原文地址: https://blog.csdn.net/zz3111057382/article/details/71054954
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞