洛谷 P11328 Gym Badges 题解

题目传送门:P11328 [NOISG 2022 Finals] Gym Badges

闲话

P4053 [JSOI2007] 建筑抢修 讨论区的多倍经验帖来的,直接把原题代码改了输入输出提交发现:补兑! 所以大家在看多倍经验的时候还是得老实看题啊(叹气)。

题意大意

\(N\) 个比赛。第 \(i\) 个比赛可以用 \(L_i\)\(X_i\) 来描述。当前等级 \(\le L_i\) 时,那么可以参加第 \(i\) 个比赛,等级提升 \(X_i\)

初始等级为 \(0\),问最多可以参加多少场比赛。

思路分析

P4053 [JSOI2007] 建筑抢修 一样,是一个反悔贪心。

原来的贪心策略如下。

设当前的等级为 \(now\)。因为等级要求更低的比赛会在等级提升的过程中先无法参加,所以我们先参加等级要求低的比赛。我们就以等级要求 \(L_i\) 为第一关键字将比赛从小到大排序。

当目前参加比赛数 \(ans\) 一定时,我们还是先让 \(now\) 尽量小一点。从这一点就可以想出反悔机制:参加到第 \(i\) 场比赛时,如果 \(L_i \ge now\),我们就参加这次比赛,\(ans + 1\),并将这次比赛加入以提升等级 \(X_i\) 为关键字的大根堆中;如果 $ L_i < now$ ,那么取出大根堆的堆顶,若堆顶元素的提升等级比当前比赛多,那将当前比赛替换掉在堆顶的比赛,\(now\) 减去堆顶,再将本次比赛存入大根堆里,\(ans\) 值不变。

如此将 \(n\) 场比赛都扫一遍,就可以得出答案。

但是和建筑抢修不一样的是,建筑抢修的要求为现在的时间 \(now\) 加上当前建筑的修理时间 \(X_i\) 小于规定时间 \(L_i\),而本题中要求现在的等级 \(now\) 小于规定等级 \(L_i\) 时才能参加比赛,比赛后等级加 \(x_i\)

这样的话我们就发现:用 \(L_i\) 作为关键字排序好像不能保证正确性。

那么我们想一想,假设现在的等级为 \(now\),我们有两场比赛,它们的等级要求分别为 \(L_a\)\(L_b\),提升等级分别为 \(X_a\)\(X_b\),那么只有在参加了比赛 a 之后我们不能参加比赛 b,但参加了比赛 b 之后还可以参加比赛 a 时,我们会先参加比赛 b,即 \(now + X_b \le L_a\)\(now + X_a > L_b\)。由此推知 \(X_b + L_b \le X_a + L_a\) 时先选择比赛 b 更优。如此,在给比赛排序时以 \(L_i + X_i\) 为关键字从小到大排序即可。其他贪心方法不变。

其余细节实现请见代码。

代码

#include <bits/stdc++.h>
using namespace std;

long long n, ans, now;
long long a, b;

struct node{
	long long X, L;
}num[500010];

priority_queue <long long> q;//优先队列默认大根堆 

bool cmp(node x, node y){ 
	return x.L + x.X < y.L + y.X;//以L_i + x_i 为关键字从小到大排序 
}

int main(){
	ios::sync_with_stdio(false);//解绑,加快输入输出速度 
	cin.tie(0);
	cout.tie(0);
	
	cin >> n;
	for(int i = 1; i <= n; i++){
		cin >> num[i].X;
	}
	for(int i = 1; i <= n; i++){
		cin >> num[i].L;
	}
	
	sort(num + 1, num + 1 + n, cmp);//排序
	
	for(int i = 1; i <= n ;i++){
		if(now <= num[i].L){
			ans++;
			now += num[i].X;
			q.push(num[i].X);//将该比赛入堆 
		}else{
			if(q.top() > num[i].X){//反悔操作 
				now += num[i].X;
				now -= q.top();
				q.pop();
				q.push(num[i].X);			
			}
		}
	}
	
	cout << ans << '\n';
	return 0;
} 

写在最后

帮到你的话请点赞题解喵,点赞题解谢谢喵

posted @ 2025-07-21 21:04  牢毛暂时好耶  阅读(15)  评论(0)    收藏  举报