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

题目

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

n , w , v n,w,v nwv,均为 1 ~ 100 1~100 1100 W W W 1 ~ 10000 1~10000 110000.

样例输入
4
2 1 3 2
3 2 4 2
5

n = 4 n=4 n=4个物品,重量 w w w分别是 2 , 1 , 3 , 2 2,1,3,2 2132,价值 v v v分别是 3 , 2 , 4 , 2 3,2,4,2 3242,背包重量 W W W 5 5 5.

样例输出
7

选出 0 , 1 , 3 0,1,3 013号物品,刚好装满背包,且总价值最大,为 7 7 7.

算法1

d p [ i ] [ j ] dp[i][j] dp[i][j]表示从前 i i i个物品( 0 ~ i − 1 0~i-1 0i1号)中选出重量不超过 j j j的总价值。如果不选择第 i i i个物品,则 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j];如果选择第 i i i个物品,则 d p [ i ] [ j ] = d p [ i − 1 ] [ j − w [ i − 1 ] ] + v [ i − 1 ] dp[i][j]=dp[i-1][j-w[i-1]]+v[i-1] dp[i][j]=dp[i1][jw[i1]]+v[i1](不过要注意判断 j ≥ w [ i − 1 ] j≥w[i-1] jw[i1],如果不成立说明背包装不下第 i i i个物品),这样可得递推关系:
d p [ i ] [ j ] = { d p [ i ] [ j ] = m a x { d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i − 1 ] ] + v [ i − 1 ] } , j ≥ w [ i − 1 ] d p [ i ] [ j ] = d p [ i − 1 ] [ 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[i][j]={dp[i][j]=max{dp[i1][j],dp[i1][jw[i1]]+v[i1]},jw[i1]dp[i][j]=dp[i1][j],j<w[i]
其中初始值为 d p [ 0 ] [ j ] = 0 dp[0][j]=0 dp[0][j]=0

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

由于要从 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]更新到 d p [ n ] [ W ] dp[n][W] dp[n][W],所以复杂度为 O ( 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

如果改变题目对输入数据的约束:
n , v n,v nv,均为 1 ~ 100 1~100 1100 w w w 1 ~ 1 0 7 1~10^7 1107 W W W 1 ~ 1 0 9 1~10^9 1109.

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

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

如果不选择第i个物品,则dp[i][j]对应从前 i − 1 i-1 i1个物品中选出价值为 j j j,即 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j];如果选择第 i i i个物品,则 d p [ i ] [ j ] dp[i][j] dp[i][j]对应 d p [ i − 1 ] [ j − v [ i − 1 ] ] + w [ i − 1 ] dp[i-1][j-v[i-1]]+w[i-1] dp[i1][jv[i1]]+w[i1](记得先判断 j ≥ v [ i − 1 ] j≥v[i-1] jv[i1]),这样得到递推关系:
d p [ i ] [ j ] = { d p [ i ] [ j ] = m i n { d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v [ i − 1 ] ] + w [ i − 1 ] } , j ≥ v [ i − 1 ] d p [ i ] [ j ] = d p [ i − 1 ] [ 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[i][j]={dp[i][j]=min{dp[i1][j],dp[i1][jv[i1]]+w[i1]},jv[i1]dp[i][j]=dp[i1][j],j<v[i]
初始值为 d p [ 0 ] [ 0 ] = 0 dp[0][0]=0 dp[0][0]=0 d p [ 0 ] [ 非 零 ] = I N F dp[0][非零]=INF dp[0][]=INF。输出满足 d p [ n ] [ j ] < W dp[n][j]<W dp[n][j]<W j j j的最大值。

#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值