反悔贪心学习笔记

简介

反悔贪心是用来解决这么一个问题的:当一个贪心的策略由于有后效性,在前面的选择中选出了较劣的选项,导致后面无法选到全局最优解。

顾名思义,反悔贪心即为选择的时候添加了一个“反悔”的选项,使得我们在后面遇到较优的选择时,可以反悔之前选择的较劣的选项。

反悔贪心分为两类,一种是显式反悔,一种是隐式反悔。显式反悔指的是,我们每次遇到了一个因为之前较劣的选择而无法选择的选项时,显式地考虑是否反悔之前的选项。隐式反悔指的是维护一个反悔自动机,自动支持反悔操作。

例题:洛谷 2949 Work Scheduling G

为了选择较多的工作完成,一个比较显而易见的贪心策略是,按照每个工作的最迟截止时间排序,优先选截止时间早的,截止实现相同的按价值排序。

然而这个贪心并不正确。假如截止时间早的都是低价值工作,因为选了太多低价值工作,导致后面的高价值工作没有时间做。

于是我们考虑反悔贪心。首先将所有任务按截止时间排序,接下来对于每一个任务我们都尝试选择它,同时用一个小根堆维护我们所有已选择的任务的价值。如果遇到一个任务我们无法选择,则丢掉当前价值最小的任务,选择这个任务(如果这个任务是最小的那就啥都不干)。

证明:设当前算法选出的集合为 \(S\),与 \(S\) 相同元素最多的最优解集合为 \(S'\),考虑第一个在 \(S'\) 而不在 \(S\) 中的任务为 \(j\)。不妨设我们的贪心选择了元素 \(i\) 而不是 \(j\)(比如反悔时用 \(i\) 替换了 \(j\),或者在选择时 \(i\) 是利润最小的那个),此时一定有 \(p_i\ge p_j\)。此时我们考虑将 \(j\) 替换为 \(i\),显然无论哪种情况,替换后的 \(S''\) 仍然是一个合法的集合,又因为 \(p_i\ge p_j\),我们又得到了一个最优解 \(S''\),这个最优解与 \(S\) 的相同元素比 \(S'\) 更多,与假设矛盾,故我们的做法会选出最优解。

const int MAXN = 1e5 + 5;
struct _node {
	int d, p;
	bool operator < (const _node b) const {
		return d < b.d;
	}
} a[MAXN];
int n;

void work() {
	cin >> n;
	for (int i = 1; i <= n; ++i) cin >> a[i].d >> a[i].p;
	sort(a + 1, a + 1 + n);
	priority_queue<int, vector<int>, greater<int>> pq;
	int t = 0;
	for (int i = 1; i <= n; ++i) {
		if (t < a[i].d) {
			pq.push(a[i].p);
			++t;
		} else {
			if (pq.top() >= a[i].p) continue;
			pq.pop(); pq.push(a[i].p);
		}
	}
	int ans = 0;
	while (pq.size()) {
		ans += pq.top(); pq.pop();
	}
	cout << ans << endl;
}

例题:CF865D Buy Low Sell High

考虑如果我们要在第 \(i\) 天卖出,则我们会选择之前的价格最低的一天 \(j\) 买入,获得 \(p_i-p_j\) 的收益。

考虑一个朴素的贪心,我们对于每一天,找之前的价格最低的没有操作的一天买入,然后卖出。但是这个贪心不是很正确,因为我们可能会把一个股票提前卖了,导致最后收益不高。

考虑如果我们在第 \(i\) 天买入,第 \(j\) 天卖出,我们其实可以看成在第 \(i\) 天买入,第 \(k(i<k<j)\) 天卖出,然后再在第 \(k\) 天买入,第 \(j\) 天卖出,最后我们赚的钱一样多。

所以我们可以考虑用小根堆维护每一天的价格。接下来对于每一天,我们找到之前的价格最低的一天,然后买入。

如果我们当前在第 \(i\) 天,买入了第 \(j\) 天的股票,我们可以往小根堆里加一个虚拟的价格 \(p_i\),代表我们可以返回掉在第 \(i\) 天买入的第 \(j\) 天的股票,基于以上的分析。

void work() {
	int n, ans = 0; cin >> n;
	priority_queue<int, vector<int>, greater<int>> pq;
	while (n--) {
		int x; cin >> x;
		if (pq.size() && pq.top() < x) {
			ans += x - pq.top(); pq.pop();
			pq.push(x);
		}
		pq.push(x);
	}
	cout << ans << endl;
}
posted @ 2025-10-11 10:36  小蛐蛐awa  阅读(29)  评论(0)    收藏  举报