洛谷 P2949 [USACO09OPEN] Work Scheduling G (反悔贪心)
反悔贪心
反悔贪心 第一次听,来篇题解记录一下。
题目传送门
思路
假设所有已经完成的工作所组成的集合是 \(S\)。
现在遇到一个新的工作 \(i\):
如果 \(i\) 还没有过期,那么就直接完成这个工作,把对应收益加上,并将这个工作加入到集合 \(S\) 中;
如果它过期了,并且 \(S\) 中有一个工作 \(j\) 的收益比它低,那么我们就 用当前工作 \(i\) 替换掉 \(j\),并把 \(j\) 移出 \(S\)、把 \(i\) 加入 \(S\)。 相当于是在原本做工作 \(j\) 的那天该做工作 \(i\)。
很显然这样目前总收益会变大。
那如何使这次 反悔 收益最大呢?
答案就是选 \(S\) 中收益最小的那个工作替换。
具体实现
首先要按日期将工作从小到大排序。
关于 "从集合 \(S\) 中选择收益最小的替换" 这一操作,可以用一个小根堆维护目前收益最小的那个工作。
#include <bits/stdc++.h>
#define mkpr make_pair
#define fir first
#define sec second
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef __int128 INT;
typedef pair<int, int> pii;
const int maxn = 1e5 + 7;
const int maxk = 2e1 + 7;
const int inf = 0x3f3f3f3f;
int n;
pii a[maxn];
priority_queue<int, vector<int>, greater<int> > q;
ll ans;
bool cmp(pii x, pii y) {return x.fir < y.fir;}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d%d", &a[i].fir, &a[i].sec);
sort(a + 1, a + n + 1, cmp);
for (int i = 1; i <= n; ++i) {
// 如果这个截止日期已经过了
// 那就考虑反悔
if (a[i].fir <= q.size()) {
// 选用价值最低的来替换
if (a[i].sec > q.top()) {
q.pop();
q.push(a[i].sec);
}
} else { // 截至日期还没到, 可以直接做
q.push(a[i].sec);
}
}
// 求和
while (q.size())
ans += q.top(), q.pop();
printf("%lld\n", ans);
return 0;
}
当然,选择一边操作一边更新 \(ans\) 也是可以的。

浙公网安备 33010602011771号