01背包问题
来源
Description
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
Input
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
Output
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
Sample Input
4 5
1 2
2 4
3 4
4 5
Sample Output
8
分析问题:
-
01背包问题的特点:每个物品只有一件,只有取或者不取两种状态
-
由于背包问题是dp的一个大类,所以我们也这里采用dp的逻辑框架来思考问题:
-
状态表示:
dp[i][j]
表示在包括前i种物品,容积为j情况下的最大价值 -
状态转移方程:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])
MOOC的陈越老师说过越简单的算法包含的信息量往往越大,第一次看到这个方程先别晕,我们来慢慢将它解释清楚.
由于状态转移方程代表的两个状态之间的联系,所以让我们思考一下到
dp[i][j]
有哪些“路径”:- 显然每个物品的状态都有拿/不拿两种状态
dp[i-1][j]
:先思考不拿的情况,显然dp[i][j]
的状态可以直接由dp[i-1][j]
得到dp[i-1][j-w[i]]+v[i]
:再思考拿的情况,此时我们需要先保证对应的体积是满足j-w[i]>0
的,对应的上一个状态也不应该是简单的dp[i-1][j-1]
,而是dp[i-1][j-w[i]]
,此时再加上当前物品的价值即可
一个明显的事实是转态转移的过程应该是从无到有,对应的下标也应该是从小到大的,
所以我们这里也是从对应的这两个维度去讨论.
-
初始化:由于
dp[0][0]
代表的是0种物品,0体积下的最大价值,所以初值应该为0
#include <stdio.h> #include <stdlib.h> #include <iostream> #include <algorithm> #include <string.h> #define INF 0x3f3f3f3f using namespace std; const int N=1e3; int dp[N+10][N+10],w[N],v[N]; //dp[i][j]表示到第i种商品,恰好在j体积下的最大价值 int main() { int n,m; while(~scanf("%d %d",&n,&m)){ memset(dp,0,sizeof dp); for(int i=1;i<=n;i++){ scanf("%d %d",&w[i],&v[i]); } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ //由于dp的定义,我们显然也要保证j<w[i]状态的初始化,因为我们在机型dp[i-1][j-w[i]]的时候很可能要使用到对应的状态 dp[i][j]=dp[i-1][j]; if(j>=w[i]){ dp[i][j]=max(dp[i][j],dp[i-1][j-w[i]]+v[i]); //两种状态:拿,或者不打 } } } cout << dp[n][m] << endl; } }
-
-
思考一下在dp的框架下如何优化这个问题,我们是否可以将状态方程由二维转为一维:
-
观察上式,我们可以看出
dp[i][j]
在i维上的状态始终只与i-1维有关,所以我们可以对其进行优化 -
状态表达式:
dp[j]
表示i体积下的最大价值 -
状态转移方程:
dp[j]=max(dp[j],dp[j-w[i]]+v[i])
注:关于这里为什么dp[j],大家可以对二维状态表达式方法做一个等价变换以帮助理解
-
初始化:dp[0]=0
-
注意点:和上一题不同的是,在二维存储的状态下,我们可以通过
i-1
保证对于第i件的一次取或不取的操作.但是在一维状态,我们无法确保当前操作的dp[j]是否已包含了第i件物品.我们以一次初始化为例查看这个问题(字丑,勿怪):
解决:将j从大到小枚举
#include <stdio.h> #include <stdlib.h> #include <iostream> #include <algorithm> #include <string.h> #define INF 0x3f3f3f3f using namespace std; const int N=1e3; int dp[N+10],w[N],v[N]; //dp[i]表示到i体积下的最大价值 int main() { int n,m; while(~scanf("%d %d",&n,&m)){ memset(dp,0,sizeof dp); for(int i=1;i<=n;i++){ scanf("%d %d",&w[i],&v[i]); } for(int i=1;i<=n;i++){ for(int j=m;j>=w[i];j--){ //保证了0/1的特性 dp[j]=max(dp[j],dp[j-w[i]]+v[i]); //注意到在求max的时候,体积维度的值始终是不需要通过-1初始化的 } } cout << dp[m] << endl; } }
-