0. 前言
本文要介绍的一道题是动态规划的经典变种------二维费用背包问题,这是华为机考的第三题,也就是压轴题,有一定难度,下面我们进入正题。
1. 题目描述
小红在遗迹中收集宝藏。
遗迹中共有 n 件宝藏,收集第 i 件宝藏需要花费的时间为 t_i,价值为 p_i。
总探险时间不能超过 T,若 t_i > 30,则视为重型宝藏,重型宝藏的总数不得超过 m 个,并且每件宝藏只能收集一次。
满足上面限制的条件下,求小红能获得的最大价值总和。
输入和输出描述如下:


输出:一个整数,表示小红能获得的最大总价值。
2. 题目分析
在上一篇的幻兽防御战中,可以靠 排序+贪心 轻松解决。但在本题中:
- 重型宝藏虽然价值高,但是它会占用重型宝藏的名额,并且耗时往往更长。
- 选择了一个重型宝藏,可能会导致无法选择后面更优的两个普通宝藏的组合。
- 当存在 互斥的多种约束 时,局部最优不等于全局最优,这时我们需要 穷举所有状态并记录最优解 ,这正是 动态规划 存在的意义。
3. 动态规划思路分析
3.1 确定dp数组的含义
本题中存在两个限制,分别是总时间 T 和重型宝藏名额 m。
我们定义:dp[j][k] 代表在剩余时间为 j,剩余重型名额为 k 时,能获得的最大价值。
3.2 确定状态转移方程
对于每一件宝藏,我们需要选择拿还是不拿。
- 如果不拿,那么
dp[j][k]保持不变。 - 当剩余资源
j和k允许时,我们可以选择拿,拿了之后需要判断是普通宝藏还是重型宝藏:- 普通宝藏 :
t_i <= 30,dp[j][k] = max(dp[j][k], dp[j - t_i][k] + p_i)。 - 重型宝藏 :
t_i > 30,dp[j][k] = max(dp[j][k], dp[j - t_i][k - 1] + p_i)
- 普通宝藏 :
3.3 其他需要注意的点
所有的 dp[j][k] 初始化为 0,代表初始状态下没有收集任何宝藏。
为了保证每件宝藏只被收集一次,内层循环(时间 j 和名额 k)必须采用 逆序 遍历。
按照这个思路,每一轮循环都会基于上一轮的结果更新当前的最高价值。
4. 代码实现
完整代码如下:
cpp
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
using namespace std;
int main() {
string line;
getline(cin, line);
stringstream ss(line);
int n,T,m;
ss >> n >>T >> m;
vector<vector<int>> dp(T+1, vector<int> (m+1, 0));
for(int i=0; i<n; i++)
{
int t_i,p_i;
getline(cin, line);
stringstream ss1(line);
ss1 >> t_i >> p_i;//读到每件宝藏的时间和价值
if(t_i > 30)//重型宝藏
{
for(int j=T; j>=t_i; j--)
{
for(int k=m; k>=1; k--)
{
dp[j][k] = max(dp[j][k], dp[j-t_i][k-1]+p_i);
}
}
}
else
{
for(int j=T; j>=t_i; j--)
{
for(int k=m; k>=0; k--)
{
dp[j][k] = max(dp[j][k], dp[j-t_i][k]+p_i);
}
}
}
}
cout << dp[T][m];
return 0;
}
5. 几类动态规划问题方法梳理
我将常见的几类动态规划问题整理成下面的表格:
