反悔贪心

\(\text{luogu-4053}\)

\(n\) 个建筑需要维修,每个建筑分别需要 \(x_i\) 的时间维修并且在 \(t_i\) 之前维修完成。

求至多能维修的建筑数。

\(1 \le n \le 1.5 \times 10^5\)\(1 \le x_i < t_i \le 2^{31} - 1\)


反悔贪心经典好题。

首先贪心的想,显然要按 \(t_i\) 排序,以此来安排维修顺序,接着会有两种情况:

  • \(res+x_i \le t_i\),则可以直接维修 \(i\),答案累加 \(1\)
  • \(res+x_i > t_i\),那么我们就需要把前面耗时最多的反悔掉,选当前的 \(i\),因为这样更优。

反悔的过程可以用优先队列维护。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 150005

long long read() {
	long long x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

struct node { long long x, t; } a[MAXN];
priority_queue<long long> q;
long long n, res, ans;

bool cmp(node l, node r) { return l.t < r.t; }

int main() {
	n = read();
	for(int i = 1; i <= n; i ++) 
		a[i].x = read(), a[i].t = read();
	sort(a + 1, a + n + 1, cmp);
	for(int i = 1; i <= n; i ++) {
		q.push(a[i].x), res += a[i].x;
		if(res <= a[i].t) ans ++;
		else res -= q.top(), q.pop();
	}
	cout << ans << "\n";
	return 0;
}

\(\text{luogu-11457}\)

\(n\) 个工作,每个工作需要在 \(s_i\) 之前开始并且将花费 \(t_i\) 时间。

求最多能完成的工作数。

\(1 \le T \le 10\)\(1 \le n \le 2 \times 10^5\)\(1 \le \sum n \le 3 \times 10^5\)\(0 \le s_i \le 10^{18}\)\(1 \le t_i \le 10^{18}\)


跟上一题唯一的区别在于,上一题给出的是最晚期限,而这道题给出的是最晚开始时刻。

其实可以转化,显然 \(s_i+t_i\) 就是最晚期限,按照 \(s_i+t_i\) 排序即可。

\(\text{luogu-11328}\)

\(n\) 个可参加的比赛,每个比赛有等级限制 \(l_i\),超过将不能参加,参加完第 \(i\) 个比赛将提升 \(x_i\) 等级。

求最多能参加的比赛数。

\(1 \le n \le 5 \times 10^5\)\(1 \le x_i,l_i \le 10^9\)


实质上跟 \(\text{luogu-11457}\) 一摸一样。

\(\text{zzfls-P118}/\text{luogu-11268}\)

你要买 \(n\) 个物品,第 \(i\) 个物品原价 \(a_i\) 元,折扣价 \(b_i\) 元(保证 \(b_i \le a_i\))。

你还有 \(m\) 个满减优惠券,第 \(j\) 个优惠券形如原价\(w_j\)\(v_j\)(保证 \(v_j \le w_j\))。

对于第 \(i\) 个物品,你可以选择以下三种购买方式之一:

  1. 使用原价 \(a_i\) 购买。
  2. 使用折扣价 \(b_i\) 购买。
  3. 选择一个未使用过的优惠券 \(j\),要求满足 \(a_i \ge w_j\),使用优惠券 \(j\),以 \(a_i - v_j\) 的价格购买。注意每个优惠券 \(j\) 只能被最多一个 \(i\) 使用。

求购买所有物品最少用钱。

\(1 \le n,m \le 10^6\)\(1 \le a_i,b_i,w_j,v_j \le 10^9\)\(b_i \le a_i\)\(v_j \le w_j\)


模拟赛 \(\text{A}\) 题,蓝。

赛时一直没想到正解,正解真不难,记录一下正解吧。

考虑如下做法:

  • 添加 \(n\) 张限制为 \(a_i\),优惠为 \(a_i-b_i\) 的虚空优惠券。
  • 然后仅考虑 \(n\) 件商品的原价以及这 \(n+m\) 张优惠券,用它们进行朴素贪心 \(^\dagger\),得到的答案即为本题的答案。

\(\dagger\):参考 ABC308F,将物品以及优惠券排序后从小到大枚举物品,然后将满足限制的优惠券加到大根堆中贪心的使用最大的优惠券。

我们考虑为什么这么做是对的:容易发现这么做唯一的问题在于可能有一个原价不为 \(a_i\) 的商品 \(a_j\) 使用了 \(a_i\) 产生的虚空优惠券,根据贪心这个时候 \(a_i\) 会使用另一张优惠券减 \(x\) 元,记这一张优惠券的限制为 \(lim\)

可以发现 \(a_j\) 会抢走 \(a_i\) 的虚空优惠券的一个必要条件是 \(a_j\geq a_i\),又有 \(lim\leq a_i \leq a_j\),故此时交换 \(a_i\)\(a_j\) 的优惠券可以使得 \(a_i\) 使用自己产生的虚空优惠券,即使用原先的 \(b_i\) 折扣,并且不会产生任何额外代价。

故经过若干次交换可以使得每一个商品使用原先给出的优惠券以及自己的虚空优惠券,并且在此限制条件下不会产生比朴素贪心更高的代价,故正确性得证。

时间复杂度 \(O(n\log (n+m))\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 2000005
#define pii pair<long long, long long>
#define fi first
#define se second

long long read() {
	long long x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

long long n, m, a[MAXN], ans;
priority_queue<pii> q;
pii b[MAXN];

int main() {
	n = read(), m = read();
	for(int i = 1; i <= n; i ++) {
		long long x = read(), y = read();
		a[i] = x, b[m + i] = {x, x - y};
	}
	for(int i = 1; i <= m; i ++) 
		b[i].fi = read(), b[i].se = read();
	sort(a + 1, a + n + 1);
	sort(b + 1, b + n + m + 1);
	long long mk = 0;
	for(int i = 1; i <= n; i ++) {
		while(mk < n + m && b[mk + 1].fi <= a[i]) 
			q.push({b[++ mk].se, b[mk].fi});
		if(!q.empty()) ans += (a[i] - q.top().fi), q.pop();
		else ans += a[i];
	}
	cout << ans << "\n";
	return 0;
}

posted @ 2025-11-27 19:24  So_noSlack  阅读(12)  评论(0)    收藏  举报