【CF】2020 CCPC-秦皇岛 E-Exam Results 题解

文章讲述了在一个大型考试中,如何利用数据结构如multiset和二分查找来确定及格线,从而最大化通过考试的学生数量。通过枚举最高分并维护一个有序的bi集合,作者提出了一种O(nlogn)复杂度的解决方案。
摘要由CSDN通过智能技术生成

传送门:Exam Results
标签:数据结构

题目大意

在一场有n个同学参加的考试中,每位同学都有两个特殊的属性:ai和bi,当第i位同学在这场考试中有灵感时,他会获得ai的分数,否则他会获得bi的分数。假设最后这n名同学中得分最高的同学的分数是x,那么本场考试的及格线将定为x*p%,分数不低于及格线的同学将通过考试。现在你能决定每位同学是否有灵感,请你算出最多能让多少位同学通过这场考试?
输入:T组数据,每组数据两个正整数n和p(1<=n<=2e5,1<=p<=100),ai和bi(1<=ai,bi<=1e9)。
输出:一个正整数,代表通过考试的人数的最大值。

算法分析

  • 看数据范围2e5显然是O(nlogn)的算法,如果是O(n)算法应该会给1e6。说到log那肯定就是二分或者树形的数据结构了,我们看完题又会往dp的方向想,那么肯定不会是很复杂的数据结构,我们考虑set。此时我们再思考dp是否可行。乍一看每个人只有两种情况,可以用0和1分别表示两种状态,但是数据太大不能用状压dp,同时后效性无法消除,我们只能排除这个想法。
  • logn已经有了,我们想想怎么得到剩下的n,显然只有一种可能:枚举。我们枚举每个人作为最高分,然后计算这种情况下最多有多少人能及格,这个过程如果能用二分或者set解决就完美了。set虽然符合本题的要求,能够维护有序列,但ai和bi并不具有特异性,我们不需要set的去重功能,那就换成multiset。
  • 再考虑维护的数据是什么。如果没有其它限制,从贪心的角度来看,我们肯定希望最高分的同学分数较低,其它同学分数较高,这样一来及格的人数肯定是最多的。但是最明显的一个问题就是:我们如果简单地取每个同学的最高分——ai,就无法保证他的分数不超过最高分x,但如果取每位同学的bi,就无法保证这种策略最优。
  • 我们不妨创造一种单调的环境:对n个同学按ai排序,然后从前往后枚举,再用一个multiset存从i到n的所有同学的bi。那么当我们枚举到第i个同学的时候,他前面的同学都是ai比他低的,全部取ai。而在他后面的同学都是ai比他大的,全部只能选bi,此时我们先判multiset里最大的数是否比x大。如果是,则continue,否则以双指针的思想,对multiset从小到大遍历,把不及格的同学erase,然后对答案加上size()。
  • 最后还要特判一点。我们知道每个人的ai都有可能作为x,但bi作为每个人最小的属性,只有最大的bi可能作为x。这时我们只需要进行一次O(n)的遍历,就能算出特殊情况下的答案。那么本题就到此为止了,总体复杂度为O(nlogn)。

代码实现

#include <iostream>
using namespace std;
#include <algorithm>
#include <set>
multiset<long long> s;
struct Iem{
	long long a,b;
}I[200005];
bool rule(Iem a,Iem b){
	return a.a<b.a;
}
int main(){
	long long i,m,i0,i1,j,x,mw,len,T,k,y,z,ans,p,mx;
	long long n,cnt,now;
	char ch;
	bool flag;
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>T;
	for(i1=1;i1<=T;i1++){
		cin>>n>>p;
		ans=0;
		for(i=1;i<=n;i++)
			cin>>I[i].a>>I[i].b;
		sort(I+1,I+n+1,rule);
		s.clear();
		mx=0;
		for(i=1;i<=n;i++){
			mx=max(mx,I[i].b);
			s.insert(I[i].b);
		}
		now=n;
		z=1;
		for(i=1;i<=n;i++){
			x=I[i].a*p;
			if(x%100)x=x/100+1;
			else x=x/100;
			while(z<=n&&I[z].a<x)z++;
			y=i-z+1;
			cnt=0;
			if(s.count(I[i].b)){now--;s.erase(s.find(I[i].b));}
			for(auto &j:s)
				if(j<x){
					cnt++;
					now--;
				}
				else break;
			while(cnt--)
				s.erase(s.begin());
			y+=now;
			if(I[i].a>=mx)ans=max(ans,y);
		}
		cnt=0;
		x=mx*p;
		if(x%100)x=x/100+1;
		else x=x/100;
		for(i=1;i<=n;i++)
			if(I[i].b>=x||(I[i].a<=mx&&I[i].a>=x))
				cnt++;
		ans=max(ans,cnt);
		cout<<"Case #"<<i1<<": "<<ans<<'\n';
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值