题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N\) 项工作,每项工作需 \(1\) 单位时间,有截止时间 \(D_i\) 和利润 \(P_i\)。同一时刻只能做一项工作,求在截止日期前完成的工作的最大总利润。这是一个反悔贪心/优先队列问题,关键在于按截止时间排序后,用最小堆维护已选工作,当时间不足时"反悔"替换掉利润最小的工作。
-
算法选择:
- 按截止时间排序:将工作按 \(D_i\) 升序排列,便于按时间顺序处理
- 最小堆维护已选工作:用优先队列存储已选工作的利润,堆顶为最小利润
- 反悔策略:当已选工作数等于当前截止时间(时间已满),若新工作利润更高,则替换掉堆顶的最小利润工作
-
关键步骤:
- 读取输入:\(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\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log N)\),排序 \(O(N \log N)\),堆操作 \(O(N \log N)\)
- 空间复杂度:\(O(N)\),优先队列存储已选工作
-
反悔贪心的核心思想:
- 截止时间即容量:截止时间 \(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
浙公网安备 33010602011771号