题解:学而思编程 火灾

【题目来源】

学而思编程:火灾

【题目描述】

小猴的房子着火了,他想从大火中带走价值尽可能多的物品,每次他只能带走一个,救出第 \(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

【核心思想】

  1. 问题分析:给定 \(n\) 个物品,每个物品救出需要 \(t_i\) 时间、价值 \(p_i\),且必须在 \(d_i\) 时间之前救出(严格小于 \(d_i\))。每次只能救一个物品,求能带走的最大价值总和。这是一个带截止时间的01背包问题。

  2. 算法选择

    • 按截止时间排序:将物品按 \(d_i\) 升序排序,确保在考虑第 \(i\) 个物品时,所有已处理的物品截止时间都不大于它
    • 01背包DP\(f[j]\) 表示在时间 \(j\) 内能获得的最大价值
    • 逆序枚举:为保证每个物品只选一次,内层循环逆序枚举时间
  3. 关键步骤

    • 读取输入\(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\),因为必须在截止时间之前完成
    • 统计答案:遍历所有可能的时间点 \(1\)\(a[n].d - 1\),取最大值
    • 输出结果:最大价值总和 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \times D)\),其中 \(D = \max(d_i)\),排序 \(O(n \log n)\),背包 \(O(n \times D)\)
    • 空间复杂度:\(O(D)\),一维DP数组
  5. 带截止时间的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
posted @ 2026-06-18 18:03  团爸讲算法  阅读(5)  评论(0)    收藏  举报