专项:反悔贪心
CF865D
题意:给定股票每天的价值,一天只可以买入一股或卖出一股,求 \(n\) 天后的最大收益。
错误的贪心:在第 \(i\) 天卖出前 \(i - 1\) 天还没有卖出的最小价格。
用小根堆维护。
rep(i, 1, n) {
int x; cin >> x;
if(!q.empty() && q.top() < x) {
ans += x - q.top();
q.pop();
}
q.push(x);
}
对于 \(i < j < k\),若 \(a[k] - a[i] > a[j] - a[i]\),而 \(a[i]\) 早在 \(j\) 时刻就被计入答案并踢出队列了,这样显然是不优的。
考虑反悔。
什么叫反悔?也就是把第 \(j\) 天的影响消除,并在候选队列里添加 \(i\)。
-
如果第 \(j\) 天选了 \(i\) 且第 \(k\) 天选了 \(j\),由于 \((a[k] - a[j]) + (a[j] - a[i]) = a[k] - a[i]\),等同于第 \(k\) 天选了 \(i\),相当于撤销了 \(j\) 的影响。
仅这样处理是不够的。
此时第 \(j\) 天的股票已经踢出队列,但实际上还没被卖出,有可能对答案有贡献的,不能踢出。 -
如果第 \(j\) 天选了 \(i\),上述代码中的
push(x)
实际上是把一个待反悔的 \(i\) 加入队列。
只需要再push(x)
一次,将 \(j\) 加入队列。
rep(i, 1, n) {
int x; cin >> x;
if(!q.empty() && q.top() < x) {
ans += x - q.top();
q.pop();
q.push(x);
}
q.push(x);
}
P2949 [USACO09OPEN] Work Scheduling G
题意:\(n\) 个工作,每个工作有一截止时间 \(t_i\) 和价值 \(p_i\),每天能完成 \(1\) 项工作,最大化总价值。
贪心:按截止时间排序,能做就做。
反悔:不能做时,如果前面存在小于当前价值的元素,撤销操作,更新贡献。
小根堆维护已经做了的所有价值,用堆的大小表示当前时间。
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int n; cin >> n;
vector<pair<int, int>> a(n);
for(auto &[x, y] : a) cin >> x >> y;
ranges::sort(a);
priority_queue<ll, vector<ll>, greater<ll>> q;
for(auto &[x, y] : a) {
if(x > q.size()) {
q.push(y);
}
else if(y > q.top()) {
q.pop();
q.push(y);
}
}
ll ans = 0;
while(!q.empty()) {
ans += q.top();
q.pop();
}
cout << ans;
return 0;
}
P4053 [JSOI2007] 建筑抢修
题意:\(n\) 个工作,每个工作有所需时间 \(len_i\) 和截止时间 \(ed_i\),最大化完成数量。
贪心:按截止时间排序,能做就做。
反悔:不能做时,如果前面存在 \(len\) 大于当前 \(len\) 的元素,撤销操作,更新贡献。
维护当前时间 \(t\) 和已经完成的所有 \(len\)。
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int n; cin >> n;
vector<pair<int, int>> a(n);
for(auto &[ed, len] : a) cin >> len >> ed;
ranges::sort(a);
priority_queue<int> q;
ll t = 0;
for(auto &[ed, len] : a) {
if(t + len <= ed) {
t += len;
q.push(len);
}
else {
if(len < q.top()) {
t += -q.top() + len;
q.pop();
q.push(len);
}
}
}
cout << q.size();
return 0;
}
P2107 小Z的AK计划
肯定是顺着走一遍最优,每个点抉择选或不选。
贪心:能选就选。
反悔:如果已经选过的点中存在思考时间大于当前位置,撤销操作。
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
ll n, m; cin >> n >> m;
vector<pair<ll, ll>> a(n);
for(auto &[pos, val] : a) cin >> pos >> val;
ranges::sort(a);
priority_queue<ll> q;
ll x = 0;
for(auto &[pos, val] : a) {
if(pos + x + val <= m) {
x += val;
q.push(val);
}
else if(!q.empty() && val < q.top()){
x += val - q.top();
q.pop();
q.push(val);
}
}
cout << q.size();
return 0;
}
CF3D Least Cost Bracket Sequence
如果 \(s[i] = ?\),先填上 ')'。
维护 \(tp\) 等于当前左括号数减右括号数。
如果 \(tp < 0\),说明左括号少了,反悔 '?' 变为 '(',选对当前贡献最大的位置。
priority_queue<pair<int, int>> q;
string s;
ll tp, ans;
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
cin >> s;
for(int i = 0; auto ch : s) {
if(ch == '(') {
++ tp;
}
else {
-- tp;
if(ch == '?') {
int L, R; cin >> L >> R;
ans += R;
s[i] = ')';
q.emplace(R - L, i);
}
}
if(tp < 0) {
if(q.empty()) break;
tp += 2;
ans -= q.top().first;
s[q.top().second] = '(';
q.pop();
}
++ i;
}
if(tp == 0) cout << ans << '\n' << s;
else cout << -1 << '\n';
return 0;
}
CF730I
先用编程能力最大的 \(p\) 名学生组成编程队,然后从 \(0\) 开始一个人一个人地构建运动队。
分类讨论:
- 招人:从剩余学生中,选择运动能力最大的。
- 挖人:从编程队中挖一名【运动能力减编程能力】最大的学生到运动队,然后编程队再招人,从剩余学生中,选择编程能力最大的。
上面这两种方法,谁能让能力总和变得更大,就用哪种方法。重复执行 \(s\) 次。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); ++ i)
#define per(i, a, b) for(int i = (a); i >= (b); -- i)
#define pb emplace_back
using namespace std;
using ll = long long;
constexpr int N = 3e3 + 5;
int n, p, s, a[N], b[N], ans;
struct programming {
int i;
bool operator < (const programming &o) const {
if(b[i] - a[i] != b[o.i] - a[o.i]) return b[i] - a[i] > b[o.i] - a[o.i];
return i > o.i;
}
};
struct remain_p {
int i;
bool operator < (const remain_p &o) const {
if(a[i] != a[o.i]) return a[i] > a[o.i];
return i > o.i;
}
};
struct remain_s {
int i;
bool operator < (const remain_s &o) const {
if(b[i] != b[o.i]) return b[i] > b[o.i];
return i > o.i;
}
};
set<programming> se;
set<remain_p> rp;
set<remain_s> rs;
set<int> ss;
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
cin >> n >> p >> s;
for(int i = 1; i <= n; ++ i) cin >> a[i];
for(int i = 1; i <= n; ++ i) cin >> b[i];
for(int i = 1; i <= n; ++ i) {
rp.insert({i});
rs.insert({i});
}
for(int i = 1; i <= p; ++ i) {
int x = rp.begin() -> i;
se.insert({x});
rp.erase({x});
rs.erase({x});
}
for(auto [i] : se) ans += a[i];
for(int i = 1; i <= s; ++ i) {
int x = 0, y = 0;
x = b[rs.begin() -> i];
y = b[se.begin() -> i] - a[se.begin() -> i] + a[rp.begin() -> i];
if(y < x) {
ans += x;
int j = rs.begin() -> i;
rp.erase({j});
rs.erase({j});
ss.insert(j);
}
else {
ans += y;
int j = se.begin() -> i;
se.erase({j});
ss.insert(j);
j = rp.begin() -> i;
se.insert({j});
rp.erase({j});
rs.erase({j});
}
}
cout << ans << '\n';
for(auto [i] : se) cout << i << ' '; cout << '\n';
for(auto i : ss) cout << i << ' ';
return 0;
}