题目描述

猪仙发现人类可以上很多大学,而猪们却没有大学可上。为了解决这个问题,于是他创立了一所大学,取名为猪仙大学。

为了选拔合适的学生入学,他发明了一种学术能力测试(简称 CSAT),这种测试的分数异常精确,每头猪的成绩可以用 1 ~ 2 * 10 9  之间的一个整数表示。

猪仙大学的学费很贵(猪仙比较黑),很多猪都负担不起,他们需要申请一些奖学金(可是政府并没有为猪准备奖学金,于是所有的预算都必须要从学校自身有限的基金中间扣除(设基金总额为 F , 0 ≤ F ≤ 2 * 10 )。

更槽的事,猪仙大学的只有 N(1 ≤ N ≤ 19999) 间教室,N 是一个奇数,而一共有 C (N ≤ C ≤ 10  头猪申请入学,为了让最多的猪接受教育,猪仙打算接受  头猪的申请,而且她还想让这些猪 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 ,F ≤ 2 * 10 

 

  看到中位数首先口吐芬芳 很懵啊,因为基本没有遇到过关于中位数的题,看了题解 仔细想想还是有点思路,由于 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);
}

 

  完了嗷。