Moo University - Financial Aid //优先队列

题目链接

http://poj.org/problem?id=2010

题意

给定 C C C个数对儿,其中每对儿第一个数表示分数,第二个表示花费。现在要求去这 C C C个数对儿中找出 n n n个( n n n为奇数),在这 n n n个数对儿的花费总和不超过 F F F的前提下,分数的中位数最大。

思路

需要先对这 C C C个数对儿按照分数从小到大排序,我们如果能维护排序后每个数对儿:去其前面找 n 2 \frac{n}{2} 2n个数对儿且其总花费最小,和去其后找 n 2 \frac{n}{2} 2n个数对儿且其花费总和最小;则最后只需要从后往前扫一遍,寻找符合条件的即可,从后往前是因为我们把分数从小到大排的序,从后往前找保证中位数最大,只需要找固定 n n n个数的总花费不超过 F F F即可。
去一个区间找固定区间的最大值、最小值,优先级队列可以胜任,然后借助这一点来维护固定区间的最小总和。对于本题,需要求固定区间的最小值,然后我们就可以用优先队列维护一个大顶堆,当拓展区间时,只需要让堆顶元素与当前元素比较,遇到比堆顶元素更小的就及时更新求和数组和堆顶元素即可。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int maxn=1e5+10;
const ll inf=1e18;
#define pb push_back
#define ft first
#define sd second
#define ms(x,y) memset(x,y,sizeof(x))
int n,c,f;
P a[maxn];
ll lm[maxn],rm[maxn];//维护每个数对儿左边n/2个最小花费和,和右边n/2个最小花费和
priority_queue<ll> pq;//大顶堆
int main()
{
    while(~scanf("%d%d%d",&n,&c,&f)){
        while(!pq.empty())pq.pop();
        for(int i=0;i<c;i++){
            scanf("%d%d",&a[i].ft,&a[i].sd);
            lm[i]=0;rm[i]=0;
        }
        sort(a,a+c);//pair默认先按first排序,first相同的按second排序
        int tot=n/2;
        for(int i=0;i<=c-1-n/2;i++){//维护左边的
            if(i<n/2){
                pq.push(a[i].sd);
                lm[tot]+=a[i].sd;
            }
            else {
                ll no=lm[tot];
                if(a[i].sd>=pq.top()){
                    lm[++tot]=no;
                }
                else {//有更优值,及时更新
                    pq.push(a[i].sd);
                    lm[++tot]=no-pq.top()+a[i].sd;
                    pq.pop();
                }
            }
        }
        while(!pq.empty())pq.pop();
        tot=c-1-n/2;
        for(int i=c-1;i>=n/2;i--){//维护右边的
            if(i>c-1-n/2){
                pq.push(a[i].sd);
                rm[tot]+=a[i].sd;
            }
            else {
                ll no=rm[tot];
                if(a[i].sd>=pq.top()){
                    rm[--tot]=no;
                }
                else {//有更优值,及时更新
                    pq.push(a[i].sd);
                    rm[--tot]=no-pq.top()+a[i].sd;
                    pq.pop();
                }
            }
        }
        int id=-1;
        for(int i=c-1-n/2;i>=n/2;i--){//从后往前找最大合法中位数
            ll now=a[i].sd+lm[i]+rm[i];
            if(now<=f){
                id=a[i].ft;break;
            }
        }
        cout<<id<<'\n';
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值