题目描述
猪仙发现人类可以上很多大学,而猪们却没有大学可上。为了解决这个问题,于是他创立了一所大学,取名为猪仙大学。
为了选拔合适的学生入学,他发明了一种学术能力测试(简称 CSAT),这种测试的分数异常精确,每头猪的成绩可以用 1 ~ 2 * 10 9 之间的一个整数表示。
猪仙大学的学费很贵(猪仙比较黑),很多猪都负担不起,他们需要申请一些奖学金(可是政府并没有为猪准备奖学金,于是所有的预算都必须要从学校自身有限的基金中间扣除(设基金总额为 F , 0 ≤ F ≤ 2 * 10 9 )。
更槽的事,猪仙大学的只有 N(1 ≤ N ≤ 19999) 间教室,N 是一个奇数,而一共有 C (N ≤ C ≤ 10 5 ) 头猪申请入学,为了让最多的猪接受教育,猪仙打算接受 头猪的申请,而且她还想让这些猪 CSAT 成绩的中位数尽可能地高。
所谓中位数,就是在一堆数字在排序后处在最中间的那个数字,比如 { 3,8,9,7,5 } 的中位数就是 7。
给定每头猪的 CSAT 成绩和打算申请的奖学金数目,以及可以资助的基金总数,确定猪仙接受哪些猪的申请才可以使成绩的中位数达到最大。
输入格式
第一行:三个用空格分开的整数:N,C 和 F 。
第二行到 C + 1 行:每行有两个用空格分开的整数。第一个数是这头猪的 CSAT 成绩,第二个数是这头猪想申请的奖学金。
输出格式
第一行:一个整数,表示猪仙可以得到的最大中位数,如果现有基金不够资助 N 头猪,则输出 -1。
样例
样例输入
3 5 70
30 25
50 21
20 20
5 18
35 30
样例输出
35
样例1解释
猪仙接受 CSAT 分数为 3,35,50 的猪的申请,中位数为 35,需支付的奖学金总额为: 18 + 30 + 21 = 69 < 70 。
数据范围与提示
对于 30% 的数据,1 ≤ N ≤ C ≤ 100;
另有 30% 的数据,1 ≤ N ≤ 500 ,N ≤ C ≤ 5000;
对 100% 的数据,1 ≤ N ≤ 19999,N ≤ C ≤ 10 5 ,F ≤ 2 * 10 9 。
看到中位数首先口吐芬芳 很懵啊,因为基本没有遇到过关于中位数的题,看了题解 仔细想想还是有点思路,由于 N 为奇数,中位数为 N / 2 + 1,如果我们对所有人(猪)按照成绩顺序排序,那么显然前面的 N / 2 与后面的 N / 2 个是不可能作为中位
数的,因为前面(后面)根本没有 N / 2 个数 。所以我们只需要在 N / 2 + 1 ~ C - N / 2 里面找符合要求的中位数就可以了。
由于我们只关心中位数的成绩大小,因此对于中位数前后的 N 个数我们只需要关注他们最小的奖学金和,这样再加上中位数自己的奖金就得到了当前中位数的最小奖金,然后遍历每个可能的中位数,维护总奖金小于等于总资金的最大中位数,这就
是这道题的思路。
所以我们要做的是求出对于每个可能的中位数,我们需要分别求出其前后 N / 2 个奖金和的最小值。不妨设变量 sum 为当前前面 N / 2 个数(不一定连续)奖金和的最小值 。每次用 sum 更新完一个可能的中位数 i 后,由于接下来会更新在 i 之后的 j
,对于 j 来说 sum 有可能会选 i ,所以我们需要找到当前的 sum 选的 N / 2 个奖金中的最大奖金,如果最大奖金比 i 的奖金大,那么显然 sum 选 i 更优,然后我们再删去原来的最大奖金加上 i 即可 。也就是说我们需要一个可以方便的读取当前的最大值的
同时还能删去最大值同时更新最大值的数据结构,这不就是堆吗?
因此我们只需维护一个大根堆,每次更新完当前数 i 后,如果 i 的奖金比堆顶小,那么删除堆顶同时将 i 奖金数加入堆,再用一个变量 sum 维护总和即可。对于后面 N / 2 个数和的最小值只需要倒序遍历就行了。
当然由于最前面和最后面的 N / 2 个数不可能成为中位数,所以直接加入大根堆就行。
struct Stu{
ll gra,mon;
}s[maxn];
ll n,c,M,ans;
ll f[maxn],b[maxn];//f[i]表示i作为中位数前n/2的最少花费,b反之
bool cmp(Stu a,Stu b){
return a.gra>b.gra;
}
priority_queue<ll>q;
priority_queue<ll>q1;
void sol(){
Read();
sort(s+1,s+c+1,cmp);
ll sum=0;
for(int i=1;i<=n/2;i++){
q.push(s[i].mon);
sum+=s[i].mon;
}
for(int i=n/2+1;i<=c-n/2;i++){
f[i]=sum;
if(s[i].mon<q.top()){//将前n/2个数中最大值减小
sum-=q.top();
q.pop();
q.push(s[i].mon);
sum+=s[i].mon;
}
}
sum=0;
for(int i=c;i>c-n/2;i--){
q1.push(s[i].mon);
sum+=s[i].mon;
}
for(int i=c-n/2;i>=n/2+1;i--){
b[i]=sum;
if(s[i].mon<q1.top()){//将后n/2个数中最大值减小
sum-=q1.top();
q1.pop();
q1.push(s[i].mon);
sum+=s[i].mon;
}
}
当然也可以用高贵的手写堆,这样时间效率显然更高,但是相对应的有很多注意点。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
struct Stu{
ll gra,mon;
}s[maxn];
ll n,c,M,ans;
ll f[maxn],b[maxn];//f[i]表示i作为中位数前n/2的最少花费,b反之
bool cmp(Stu a,Stu b){
return a.gra>b.gra;
}
ll d[maxn];
void build(int cnt){//建堆
while(cnt!=1&&d[cnt/2]<d[cnt]){
swap(d[cnt],d[cnt/2]);
cnt/=2;
}//不断与父节点比较,直到父节点大于等于自己或者已经成为根节点了
}
void change(){
int cnt=1;
while( (cnt*2) <= n/2){//保证在 N / 2 的范围内有儿子
if(d[cnt*2]>=max(d[cnt*2+1],d[cnt])){
swap(d[cnt*2],d[cnt]);
cnt*=2;
}else if(d[cnt*2+1]>=max(d[cnt*2],d[cnt])){
swap(d[cnt*2+1],d[cnt]);
cnt=cnt*2+1;
}else break;
}//每次与两个儿子中的较大者交换
}
void sol(){
Read();
sort(s+1,s+c+1,cmp);
ll sum=0;
for(int i=1;i<=n/2;i++){
d[i]=s[i].mon;
build(i);
sum+=s[i].mon;
}
for(int i=n/2+1;i<=c-n/2;i++){
f[i]=sum;
if(s[i].mon<d[1]){//将前n/2个数中最大值减小
sum-=d[1];
d[1]=s[i].mon;
sum+=d[1];
change();
}
}
memset(d,0,sizeof(d));
sum=0;
for(int i=c;i>=c-n/2+1;i--){
d[c+1-i]=s[i].mon;
build(c+1-i);
sum+=s[i].mon;
}
for(int i=c-n/2;i>=n/2+1;i--){
b[i]=sum;
if(s[i].mon<d[1]){//将后n/2个数中最大值减小
sum-=d[1];
d[1]=s[i].mon;
sum+=d[1];
change();
}
}
for(int i=n/2+1;i<=c-n/2;i++){
if(f[i]+b[i]+s[i].mon>M) continue;
ans=max(ans,s[i].gra);
}
if(!ans)printf("-1");
else printf("%lld",ans);
}
完了嗷。
