多重背包问题解法简单分析(POJ 1276为例)

来源:互联网 发布:中国网络直播平台排名 编辑:程序博客网 时间:2024/06/08 02:38

先简单介绍几种背包问题

01背包(ZeroOnePack): 有N件物品和一个容量为V的背包, 每种物品均只有一件第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。  

完全背包(CompletePack): 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。  

多重背包(MultiplePack): 有N种物品和一个容量为V的背包,第i种物品最多有n[i]件可用。每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。  

参考:http://www.wutianqi.com/?p=539


对于多重背包问题,可以用动态规划的思路来求解,最优解的递推公式为:

减包方法(个人随便取的,不做参考):

f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i],k*c[i]<=v}

表示花费为v且且装前i种物品的收益

思路是第i种物品装k个,剩下的花费v-k*c[i]都用前i-1种来装

添包方法

已经知道了f[i-1][v],我现在要添加第i种物品数量为k

f[i][v+k*c[i]]=max{f[i][v+c*[i]],f[i-1][v]+k*w[i]}



POJ 1276 解法的启示优化方法分析:

该题为非典型的多重背包问题,其费用和价值相等。


解法1:直接利用动态规划求解

#include <iostream>
#include <cstdio>
using namespace std;
#define MAX 100001
#define N 11
int cash,n,c[N],d[N];
bool w[N][MAX];
int solve(){
    int i,j,k;
    memset(w,false,sizeof(w));
    w[0][0] = true;
    for(i=1;i<=n;i++){
        for(j=cash;j>=0;j--){
            for(k=0;k<=c[i]&&k*d[i]<=j;k++)
            {
                if(w[i][j])break;
                w[i][j] = w[i-1][j-k*d[i]];
            }
        }
        if(w[i][cash]){return cash;}
    }
    for(j=cash;j>=0;j--)
        if(w[n][j])
            return j;
}
int main(){
    int i;
    while(scanf("%d%d",&cash,&n)!=EOF){
        for(i=1;i<=n;i++)
            scanf("%d%d",&c[i],&d[i]);
        printf("%d\n",solve());
    }
    return 0;
}

结果分析:该方法直接按照动态规划的思路进行,采用减包操作,由于收益和花费相等,直接用bool来标识是否可以花费出这个量。对增加的一种新物品,给定花费x,剔除该新物品的整数倍的花费,看剩余的花费前i-1种物品是否能够达到。没有做任何优化,结果是超时。


解法2:对能得到的花费进行添包操作(优化)

#include <iostream>
#include <cstdio>
using namespace std;
#define MAX 100001
#define N 11
int cash,n,c[N],d[N];
bool w[N][MAX];
int solve(){
    int i,j,k;
    memset(w,false,sizeof(w));
    w[0][0] = true;
    int cmax = 0;
    for(i=1;i<=n;i++){
        for(j=cmax;j>=0;j--){  //添加前i-1种物品所能用到的最多花费
            if(!w[i-1][j])continue;  //不能得到这个花费的就不用计算
            for(k=0;k<=c[i]&&k*d[i]+j<=cash;k++)
            {
                w[i][j+k*d[i]] = true;
                if(j+k*d[i] > cmax)cmax=j+k*d[i];
            }
        }
        if(cmax==cash){return cash;}
    }
    return cmax;
}
int main(){
    int i;
    while(scanf("%d%d",&cash,&n)!=EOF){
        for(i=1;i<=n;i++)
            scanf("%d%d",&c[i],&d[i]);
        printf("%d\n",solve());
    }
    return 0;
}

结果分析:采用添包方法,优化两点,一是记录了前i-1种物品的最大可能花费,二是只对能花费的数进行继续添包以得到新的花费。经过优化之后运行时间约400多ms。但是显然该算法的时间复杂度还是很高,为O(nmk),n为物品种类数,m为容积,k为每类物品的数量。


解法3:叠加逐个添加(优化)

#include <iostream>
#include <cstdio>
using namespace std;
#define MAX 100001
#define N 11
int use[MAX],cash,n,c[N],d[N];
bool w[MAX];
int solve(){
    if(cash == 0 || n == 0)return 0;
    int i,j,k;
    memset(w,false,sizeof(w));
    w[0] = true;
    for(i=1;i<=n;i++){
        memset(use,0,sizeof(use));
        for(j=d[i];j<=cash;j++){
            if(!w[j] && w[j-d[i]] && use[j-d[i]]<c[i]){ //第i种物品添加得到新花费,收益大,不超量
                use[j]=use[j-d[i]]+1;  //记录新添加
                w[j]=true;
            }
        }
    }
    for(j=cash;j>=0;j--)if(w[j])return j;
}

结果分析:运行时间减少到16ms,显然是时间复杂度降到O(nm)了。这种解法和前面两种解法的主要不同在于前面解法思路清晰,那就是我在添加第i种物品之前是一定没有第i种物品的,所以要考虑第i种物品添加的量。而本解法则不需要考虑这个,因为我添加第i种物品之前可能就已经有了第i种物品。那么我该怎么来防止第i种物品超过实际存有量呢,所以在开始处理第i种物品时用一个数组来计算当花费x时用去的最小的i种物品的量。

原创粉丝点击