题目链接
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;
}