洛谷 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;
}
写在最后
帮到你的话请点赞题解喵,点赞题解谢谢喵
本文来自 er_mao_jpg ,转载请注明原文链接:https://www.cnblogs.com/er-mao-jpg233/p/18996606, 欢迎各路大佬参观投喂~

浙公网安备 33010602011771号