51NOD 1085 0-1背包问题:简单DP DP套路详解 新手向

前言

  • 基础入门的DP,属于学习DP必须掌握的类型题
  • 本章将以0-1背包问题着重介绍DP的一种常见套路。该套路可以帮助解决大量DP题目
  • 新手向
  • 欢迎交流

题目

在N件物品取出若干件放在容量为W的背包里,每件物品的体积为W1,W2……Wn(Wi为整数),与之相对应的价值为P1,P2……Pn(Pi为整数)。求背包能够容纳的最大价值。

Input

第1行,2个整数,N和W中间用空格隔开。N为物品的数量,W为背包的容量。(1 <= N <= 100,1 <= W <= 10000) 第2 ~ N+1行,每行2个整数,Wi和Pi,分别是物品的体积和物品的价值。(1 <= Wi, Pi <= 10000)

Output

输出可以容纳的最大价值。

Sample Input

3 6
2 5
3 8
4 9

Sample Output

14

算法思路

  • 本节讲解一种DP常用套路
  • 首先,该问题有几个状态?
    1. 选了哪些物品放入背包中
    2. 背包的可以容纳的重量
  • 接着,该问题的求解目标?
    1. 背包中能够承装的最大价值量
  • 然后,在任意时刻我们如何选择?(用递推思想)
    1. 接下来的这个物品是要选择装入背包中还是不装入背包中
  • 最后,状态+目标+选择+递推+初始条件,即可得到dp数组和动态转移方程。接下来逐一解析

注意这里需要添加一个限制:接下来的这个物品是非常随机的,只要没有被取的物品,无论取哪个都行,这样就很难描述下一个物品了。我们需要添加一个限制,即取物品的方向都是从0 -> N-1的,即接下来的物品一定是按第0号物品 -> 第N-1号物品的顺序取,则下一个物品一定是第i+1号物品,就可以方便地被描述。

按照上述限制,取哪些物品就可以简单地由取了前几个物品来描述,省去再想办法标记哪些物品被取走。

详解

  • 由于有两个状态+一个最值求解目标,故我们定义dp数组为二维数组,dp数组的含义就是两个状态+一个目标的组合,即: d p [ i ] [ j ] = 前 i 个 物 品 中 , 选 择 其 中 某 些 物 品 , 包 能 容 纳 的 最 大 重 量 为 j 时 , 最 大 价 值 dp[i][j]=前i个物品中,选择其中某些物品,包能容纳的最大重量为j时,最大价值 dp[i][j]=ij
  • 根据如何选择,我们可以得到动态转移方程为: d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w ] + v ) dp[i][j]=max(dp[i-1][j], dp[i-1][j-w]+v) dp[i][j]=max(dp[i1][j],dp[i1][jw]+v)递推关系是:前i个物品中选择一部分物品放入包内,包能容纳的最大质量为j时,最大价值,可以由以下两点递推出来
    1. 不放第i个物品,则前i个物品的选择和前i-1个物品的选择是相同的(满足包内重量为j)
    2. 放第i个物品,则前i-1个物品中选择物品时,包能容纳的最大重量只能有j-w
  • 考察初始条件。这部分看下面代码的注释即可。

实现

#include <iostream>
#include <algorithm>
#define MAX_N 101
#define MAX_W 10001

using namespace std;

int N=0, W=0, Max=0;
int dp[MAX_N][MAX_W];
//dp[N][W] 背包装入前0 ~ N号物品中的某几个物品,包可容纳最大重量为W时的最大价值

int main(int argc, char const *argv[])
{
    int w[MAX_N], p[MAX_N];
    //重量,价值

    ios::sync_with_stdio(false);

    cin >> N >> W;

    for(int i = 0; i < N; ++i)//输入
    {
        cin >> w[i] >> p[i];
    }
	
	
	//初始条件:
	//显然这是个二维数组,动态转移方程是从二维数组的第一行和第一列往后推的
	//故关键就在于第一行和第一列的含义以及初始化
	//dp[0][i]:只取第0号物体时,包可容纳重量为i的最大利益
	//显然,若i<w[0],0号物体装不进去,最大利益为0
	//反之,最大利益为p[0]
	//
	//dp[i][0]:从0 ~ i号物品中选取某些物品,可容纳重量为0时,包内的最大利益
	//显然根据题意,所有物品的利益都大于0,故没有任何一个物品可以装得进去
	//故dp[i][0]=0
	//初始化完毕
	
    for(int i = 0; i <= W; ++i)
    {
        if(i >= w[0])
            dp[0][i] = p[0];
        else
            dp[0][i] = 0;
    }

    for(int i = 0; i < N; ++i)
        dp[i][0]=0;

    for(int n = 1 ; n < N; ++n)//从前0 ~ n号物品中选取
    {
        for(int weight = 1; weight <= W; ++weight)
        //取的物体的总重量为weight
        {
            if(weight - w[n] >= 0)
                dp[n][weight] = max(dp[n-1][weight], dp[n-1][weight-w[n]]+p[n]);
            else
            //若weight - w[n] < 0
            //说明这个包的容量装不下第n号物品
            //那就只能不取第n号物品了
                dp[n][weight] = dp[n-1][weight];
        }
    }

    cout << dp[N-1][W] << endl;
    //为什么输出dp[N-1][W]?
    //它的含义是:取0 ~ N-1号物品中选取物品,可容纳质量为W,最大利益
    //这就是这个包能装下的最大利益
    //故答案就是它

    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值