编程训练——01揹包算法的两种解法

题目

nn个物品,重量和价值分别存储在数组ww和数组vv中,输入总重量WW,求选出不超过WW的物品最大总价值。
每个物品只能选择一次。

nwvn,w,v,均为11001~100WW1100001~10000.

样例输入

4
2 1 3 2
3 2 4 2
5

n=4n=4个物品,重量ww分别是21322,1,3,2,价值vv分别是32423,2,4,2,揹包重量WW55.

样例输出

7

选出0130,1,3号物品,刚好装满揹包,且总价值最大,为77.

算法1

dp[i][j]dp[i][j]表示从前ii个物品(0i10~i-1号)中选出重量不超过jj的总价值。如果不选择第ii个物品,则dp[i][j]=dp[i1][j]dp[i][j]=dp[i-1][j];如果选择第ii个物品,则dp[i][j]=dp[i1][jw[i1]]+v[i1]dp[i][j]=dp[i-1][j-w[i-1]]+v[i-1](不过要注意判断jw[i1]j≥w[i-1],如果不成立说明揹包装不下第ii个物品),这样可得递推关系:
dp[i][j]={dp[i][j]=maxdp[i1][j],dp[i1][jw[i1]]+v[i1],jw[i1]dp[i][j]=dp[i1][j],j<w[i] dp[i][j]=\left\{ \begin{aligned} dp[i][j]=max{dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1]},j≥w[i-1]\\ dp[i][j]=dp[i-1][j],j<w[i] \end{aligned} \right.
其中初始值为dp[0][j]=0dp[0][j]=0

最终输出dp[n][W]dp[n][W]

由于要从dp[0][0]dp[0][0]更新到dp[n][W]dp[n][W],所以复杂度为O(nW)O(nW)

代码1

#include<stdio.h>
#include<math.h>

#define maxn 105

int max(int a, int b){
    return (a>b)?a:b;
}

int main(){
    int n, W;
    int w[maxn], v[maxn];
    int dp[maxn][maxn];
    while(scanf("%d %d", &n, &W)==2){
        for(int i = 1;i <= n;i++){
            scanf("%d", w + i);
        }
        for(int i = 1;i <= n;i++){
            scanf("%d", v + i);
        }
        for(int j = 0;j <= W;j++){
            dp[0][j] = 0;
        }
        for(int i = 1;i <= n;i++){
            dp[i][0] = 0;
        }
        for(int i = 1;i <= n;i++){
            for(int j = 1;j <= W;j++){
                if(w[i]>j)dp[i][j] = dp[i-1][j];
                else
                    dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]);
            }
        }
        printf("%d\n", dp[n][W]);
    }

    return 0;
}

算法2

如果改变题目对输入数据的约束:
nvn,v,均为11001~100ww11071~10^7WW11091~10^9.

已知复杂度为O(nW)O(nW),而上述W非常大,这会导致复杂度很高。

不妨改变dp[i][j]dp[i][j]为前ii个物品(0i10~i-1号)中选出价值为jj的最小重量,则需要从dp[0][0]dp[0][0]更新到dp[n][]dp[n][所有物品总价值],然后输出满足dp[n][j]<Wdp[n][j]<Wjj的最大值。所有物品的总价值不会超过Σi=0i=n1v[i]<=10000\Sigma_{i=0}^{i=n-1}v[i]<=10000,复杂度就降下来了。

如果不选择第i个物品,则dp[i][j]对应从前i1i-1个物品中选出价值为jj,即dp[i1][j]dp[i-1][j];如果选择第ii个物品,则dp[i][j]dp[i][j]对应dp[i1][jv[i1]]+w[i1]dp[i-1][j-v[i-1]]+w[i-1](记得先判断jv[i1]j≥v[i-1]),这样得到递推关系:
dp[i][j]={dp[i][j]=mindp[i1][j],dp[i1][jv[i1]]+w[i1],jv[i1]dp[i][j]=dp[i1][j],j<v[i] dp[i][j]=\left\{ \begin{aligned} dp[i][j]=min{dp[i-1][j],dp[i-1][j-v[i-1]]+w[i-1]},j≥v[i-1]\\ dp[i][j]=dp[i-1][j],j<v[i] \end{aligned} \right.
初始值为dp[0][0]=0dp[0][0]=0dp[0][]=INFdp[0][非零]=INF。输出满足dp[n][j]<Wdp[n][j]<Wjj的最大值。

#include<stdio.h>

#define maxn 105
#define INF 1000000005	// 所有物品总重最多为10亿
#define maxv 105

int min(int a, int b){
    return (a<b)?a:b;
}

int main(){
    int n, W;
    int v[maxn], w[maxn];
    int dp[maxn][maxn * maxv];
    int totalvalue, res;
    while(scanf("%d", &n)==1){
        for(int i = 0;i < n;i++){
            scanf("%d", &w[i]);
        }
        totalvalue = 0;
        for(int i = 0;i < n;i++){
            scanf("%d", &v[i]);
            totalvalue += v[i];
        }
        scanf("%d", & W);
        for(int j = 1;j <= totalvalue;j++){
            dp[0][j] = INF;
        }
        dp[0][0] = 0;
        for(int i = 1;i <= n;i++){
            for(int j = 0;j <= totalvalue;j++){
                if(j>=v[i-1]){
                    dp[i][j] = min(dp[i-1][j],dp[i-1][j-v[i-1]]+w[i-1]);
                }
                else dp[i][j] = dp[i-1][j];
            }
        }

        // 输出dp[n][j]中不超过W的最大的j
        res = 0;
        for(int j = totalvalue;j >= 0;j--){
            if(dp[n][j]<=W){
                res = j;
                break;
            }
        }
        printf("%d\n", res);
    }

    return 0;
}
发布了40 篇原创文章 · 获赞 43 · 访问量 3万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章