题目来源:牛客网 01背包
现有一个容量大小为V的背包和N件物品,每件物品有两个属性,体积和价值,请问这个背包最多能装价值为多少的物品
输入描述:
第一行两个整数V和n 接下来n行,每行两个整数体积和价值,1≤N≤1000,1≤V≤20000 每件物品的体积和价值范围在[1,500]
输出描述:
输出背包最多能装的物品价值
Example:
Input: 6 3
3 5
2 4
4 2
Output: 9
原理
根据动态规划解题步骤(问题抽象化、建立模型、寻找约束条件、判断是否满足最优性原理、找大问题与小问题的递推关系式、填表、寻找解组成)找出01背包问题的最优解以及解组成,然后编写代码实现。
动态规划与分治法类似,都是把大问题拆分成小问题,通过寻找大问题与小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。但不同的是,分治法在子问题和子子问题等上被重复计算了很多次,而动态规划则具有记忆性,通过填写表把所有已经解决的子问题答案纪录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间,所以在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。
最优性原理是动态规划的基础,最优性原理是指“多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略”。
实现细节(重要)
核心数据结构是dp[i][j],指在背包当前容量为j的情况下,前i个物品最佳组合的价值。
- 01背包的数据从
dp[0][0]开始增长,显然dp[0][0]是值为0的情况。
- 此后以先讨论前
i个物品在不同当前剩余背包容量即j的情况下的最大可容纳值。
- 用
w[i]表示当前第i件物品的体积:
- 如果
dp[i][j]中的j小于w[i],说明在当前要装的第j个物品不能被放入dp[i-1][j]表示的情况,即j<w[i]dp[i][j] = dp[i-1][j]。在这种情况下没得选,只能将第i件物品放弃,即保持dp[i-1][j]表示的情况。 - 如果
dp[i][j]中的j大于w[i],说明在当前要装的第j个物品可以被放入dp[i-1][j]表示的情况,这个时候有两种选择:放、不放。那选择标准肯定是最大化的价值,即j>=w[i]dp[i,j] = max {dp[i-1,j],dp[i-1,j-w[i]]+v[i]},其比较过程在max函数中完成。关键是理解不放的时候比放的时候价值还要大的情况,即同样在前i-1的种类中,可能dp[i-1,j-w[i]]会比dp[i-1,j]小很多的情况(当前可用背包容量j-w[i]太小,即使加上了v[i]也不足以超过同样种类容量为j时的最佳方案)。
表格填完最优解即是右下角的值。
代码
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
int V = 0, n = 0;
//读入背包容量和物品数量
cin>>V>>n;
int w[n+1],v[n+1],dp[n+1][V+1];
//初始化矩阵值
for(int i = 0;i<n+1;i++){
for(int j = 0;j<V+1;j++){
dp[i][j] = 0;
}
}
//依次读入物品体积及价值
for(int i = 1; i<=n; i++){
cin>>w[i]>>v[i];
}
//按前i件物品的顺序填表
for(int i = 1;i<=n;i++){
//当当前剩余容量j小于背包的最大值时
for(int j = 1;j<=V;j++){
//代码实现两种情况
if(j<w[i]){
dp[i][j] = dp[i-1][j];
}else{
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]);
}
}
}
//输出右下角的值
cout<<dp[n][V]<<endl;
return 0;
}背包问题最优解回溯
通过上面的方法可以求出背包问题的最优解,但还不知道这个最优解由哪些商品组成,故要根据最优解回溯找出解的组成,根据填表的原理可以有如下的两种寻解方式
dp[i][j] = dp[i-1][j]时,即在i-1种物品的最优情况下,不加入(可能是由于空间不够或者是实现细节所述的非最优解情况)第i种物品是前i种物品的最优情况,即回到dp[i-1,j];dp[i,j] = dp[i-1,j-w[i]]+v[i]时,即在i-1种物品的最优情况下,加入了第i种物品,该物品是最优解组成的一部分,随后我们得回到装该物品之前,即回到dp[i-1,j-w[i]];
根据上述方法遍历表格即可获得最优解的记录。