题解:洛谷 P2949 Work Scheduling

【题目来源】

洛谷:P2949 [USACO09OPEN] Work Scheduling G - 洛谷

【题目描述】

约翰有太多的工作要做。为了让农场高效运转,他必须靠他的工作赚钱,每项工作花一个单位时间。他的工作日从 0 时刻开始,有 \(10^9\) 个单位时间。在任一时刻,他都可以选择编号 \(1\)\(N\)\(N(1 \leq N \leq 10^5)\) 项工作中的任意一项工作来完成。因为他在每个单位时间里只能做一个工作,而每项工作又有一个截止日期,所以他很难有时间完成所有\(N\)个工作,虽然还是有可能。对于第 \(i\) 个工作,有一个截止时间 \(D_i(1 \leq D_i \leq 10^9)\),如果他可以完成这个工作,那么他可以获利 \(P_i( 1\leq P_i\leq 10^9 )\)。在给定的工作利润和截止时间下,约翰能够获得的利润最大为多少。

【输入】

  • Line \(1\): A single integer: \(N\)

  • Lines \(2..N+1\): Line \(i+1\) contains two space-separated integers: $D_i $and \(P_i\)

【输出】

  • Line \(1\): A single number on a line by itself that is the maximum possible profit FJ can earn.

【输入样例】

3
2 10
1 5
1 7

【输出样例】

17

【核心思想】

  1. 问题分析:给定 \(N\) 项工作,每项工作需 \(1\) 单位时间,有截止时间 \(D_i\) 和利润 \(P_i\)。同一时刻只能做一项工作,求在截止日期前完成的工作的最大总利润。这是一个反悔贪心/优先队列问题,关键在于按截止时间排序后,用最小堆维护已选工作,当时间不足时"反悔"替换掉利润最小的工作。

  2. 算法选择

    • 按截止时间排序:将工作按 \(D_i\) 升序排列,便于按时间顺序处理
    • 最小堆维护已选工作:用优先队列存储已选工作的利润,堆顶为最小利润
    • 反悔策略:当已选工作数等于当前截止时间(时间已满),若新工作利润更高,则替换掉堆顶的最小利润工作
  3. 关键步骤

    • 读取输入\(N\)\(N\) 项工作的 \((D_i, P_i)\)
    • 排序:按 \(D_i\) 升序排列
    • 贪心处理\(i\)\(1\)\(N\)):
      • \(w[i].d > q.size()\):截止时间大于已选工作数,时间充足,直接选入堆,\(ans \mathrel{+}= P_i\)
      • \(w[i].d = q.size()\):时间恰好用完,比较当前工作利润与堆顶最小利润:
        • \(P_i > q.top()\):弹出堆顶(反悔),加入当前工作,\(ans = ans - q.top() + P_i\)
    • 输出 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log N)\),排序 \(O(N \log N)\),堆操作 \(O(N \log N)\)
    • 空间复杂度:\(O(N)\),优先队列存储已选工作
  5. 反悔贪心的核心思想

    • 截止时间即容量:截止时间 \(D_i\) 表示到该时刻最多能完成 \(D_i\) 项工作,已选工作数不能超过当前截止时间
    • 局部最优可能非全局最优:直接贪心选利润最大的工作可能导致后续高利润工作无法安排,反悔机制允许用更高利润替换已选的低利润工作
    • 最小堆实现高效反悔:堆顶始终维护已选工作中的最小利润,替换操作 \(O(\log N)\)
    • 正确性保证:按截止时间顺序处理,确保每个时刻的决策基于当前最优信息,反悔保证最终全局最优
    • 适用于"带截止时间的调度 + 利润最大化"类经典问题,核心是排序后用堆维护可替换的最优解

【算法标签】

提高+ #反悔贪心

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, ans;  // n: 任务数, ans: 最大利润
struct Node
{
    int d, p;  // d: 截止时间, p: 利润
    bool operator < (Node &x)  // 按截止时间排序
    {
        return d < x.d;
    }
} w[100005];
priority_queue<int, vector<int>, greater<int> > q;  // 最小堆,存储已选择任务的利润

signed main()
{
    cin >> n;  // 输入任务数
    for (int i = 1; i <= n; i++)
    {
        cin >> w[i].d >> w[i].p;  // 输入截止时间和利润
    }
    sort(w + 1, w + n + 1);  // 按截止时间排序
    for (int i = 1; i <= n; i++)
    {
        if (w[i].d == q.size())  // 已选任务数等于当前任务截止时间
        {
            if (w[i].p > q.top())  // 当前任务利润大于已选任务的最小利润
            {
                ans -= q.top();  // 反悔:移除最小利润任务
                q.pop();
                q.push(w[i].p);  // 选择当前任务
                ans += w[i].p;  // 更新总利润
            }
        }
        else  // 还可以选择更多任务
        {
            q.push(w[i].p);  // 选择当前任务
            ans += w[i].p;  // 更新总利润
        }
    }
    cout << ans;  // 输出最大利润
    return 0;
}

【运行结果】

3
2 10
1 5
1 7
17
posted @ 2026-08-28 15:56  团爸讲算法  阅读(7)  评论(0)    收藏  举报