专项:反悔贪心

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;
}
posted @ 2024-03-05 00:53  Lu_xZ  阅读(5)  评论(0编辑  收藏  举报