前言
- 基础入门的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常用套路
- 首先,该问题有几个状态?
- 选了哪些物品放入背包中
- 背包的可以容纳的重量
- 接着,该问题的求解目标?
- 背包中能够承装的最大价值量
- 然后,在任意时刻我们如何选择?(用递推思想)
- 接下来的这个物品是要选择装入背包中还是不装入背包中
- 最后,状态+目标+选择+递推+初始条件,即可得到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]=前i个物品中,选择其中某些物品,包能容纳的最大重量为j时,最大价值
- 根据如何选择,我们可以得到动态转移方程为:
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[i−1][j],dp[i−1][j−w]+v)递推关系是:前i个物品中选择一部分物品放入包内,包能容纳的最大质量为j时,最大价值,可以由以下两点递推出来
- 不放第i个物品,则前i个物品的选择和前i-1个物品的选择是相同的(满足包内重量为j)
- 放第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;
}