题解:学而思编程 火灾
【题目来源】
学而思编程:火灾
【题目描述】
小猴的房子着火了,他想从大火中带走价值尽可能多的物品,每次他只能带走一个,救出第 \(i\) 个物品需要的时间是 \(t_i\),价值为 \(p_i\)。
而第 \(i\) 个物品将在起火后 \(d_i\) 时间后被完全烧掉,这要求救出这个物品的时刻必须严格小于\(d_i\)。特别的,如果 \(t_i≥d_i\),该物品无法被救出。
小猴一个接一个地救出他的物品,救出物品之间的时间忽略不计。问他能救出最多多少价值的物品?
【输入】
第一行为物品总数 \(n\)
接下来 \(n\) 行,每行有三个整数 \(t_i\),\(p_i\),\(d_i\)
【输出】
输出能带走的最大的价值总和
【输入样例】
3
2 4 7
2 5 6
3 6 7
【输出样例】
11
【核心思想】
-
问题分析:给定 \(n\) 个物品,每个物品救出需要 \(t_i\) 时间、价值 \(p_i\),且必须在 \(d_i\) 时间之前救出(严格小于 \(d_i\))。每次只能救一个物品,求能带走的最大价值总和。这是一个带截止时间的01背包问题。
-
算法选择:
- 按截止时间排序:将物品按 \(d_i\) 升序排序,确保在考虑第 \(i\) 个物品时,所有已处理的物品截止时间都不大于它
- 01背包DP:\(f[j]\) 表示在时间 \(j\) 内能获得的最大价值
- 逆序枚举:为保证每个物品只选一次,内层循环逆序枚举时间
-
关键步骤:
- 读取输入:\(n\)(物品数量),每个物品的 \(t_i, p_i, d_i\)
- 按截止时间排序:
sort(a + 1, a + n + 1, cmp),按 \(d_i\) 升序 - 01背包DP(\(i\) 从 \(1\) 到 \(n\)):
- 逆序枚举时间 \(j\) 从 \(a[i].d - 1\) 到 \(a[i].t\):
- \(f[j] = \max(f[j], f[j - a[i].t] + a[i].p)\)
- 注意:\(j\) 的上限是 \(a[i].d - 1\),因为必须在截止时间之前完成
- 逆序枚举时间 \(j\) 从 \(a[i].d - 1\) 到 \(a[i].t\):
- 统计答案:遍历所有可能的时间点 \(1\) 到 \(a[n].d - 1\),取最大值
- 输出结果:最大价值总和 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times D)\),其中 \(D = \max(d_i)\),排序 \(O(n \log n)\),背包 \(O(n \times D)\)
- 空间复杂度:\(O(D)\),一维DP数组
-
带截止时间的01背包核心思想:
- 排序预处理:按截止时间排序后,每个物品的可用时间范围是独立的,不会互相干扰
- 时间上限限制:对于物品 \(i\),枚举时间上限为 \(d_i - 1\),确保在截止时间前完成
- 逆序枚举保证:内层逆序枚举确保每个物品只被选取一次,符合01背包特性
- 一维数组优化:使用滚动数组将空间复杂度从 \(O(n \times D)\) 优化到 \(O(D)\)
- 适用于带时间限制的资源分配、任务调度类问题
【算法标签】
01背包
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// ================= 常量与全局变量 =================
const int N = 1005; // 最大物品数量
int n; // 物品数量
int f[20005]; // 动态规划数组,f[j] 表示在时间 j 内能获得的最大价值
int ans = -1e9; // 最终答案,初始化为极小值
// 物品结构体
struct Node
{
int t; // 耗时
int p; // 价值
int d; // 截止时间
} a[N];
// ================= 按截止时间升序排序 =================
bool cmp(Node x, Node y)
{
return x.d < y.d;
}
// ================= 主函数 =================
int main()
{
// 读取物品数量
cin >> n;
// 读取每个物品的耗时、价值和截止时间
for (int i = 1; i <= n; i++)
cin >> a[i].t >> a[i].p >> a[i].d;
// 按截止时间升序排序
sort(a + 1, a + n + 1, cmp);
// 01 背包:每个物品只能选一次
for (int i = 1; i <= n; i++)
{
// 逆序枚举时间,保证每个物品只使用一次
// 时间范围:[a[i].t, a[i].d - 1]
// 必须在截止时间之前完成,所以最晚结束时间为 a[i].d - 1
for (int j = a[i].d - 1; j >= a[i].t; j--)
{
f[j] = max(f[j], f[j - a[i].t] + a[i].p);
}
}
// 在所有可能的时间点中取最大值
// 最大截止时间为 a[n].d,因此枚举到 a[n].d - 1
for (int i = 1; i <= a[n].d - 1; i++)
ans = max(ans, f[i]);
// 输出结果
cout << ans << endl;
return 0;
}
【运行结果】
3
2 4 7
2 5 6
3 6 7
11
浙公网安备 33010602011771号