分析:
很巧妙的一个堆维护+枚举。
在对成绩排序后,我们无需知道一个成绩中位数左右两侧的选择具体是谁,只需知道左右两侧各取n/2个(保证其为中位数)时学费最小和就可以判断此数是否符合题意。
具体操作:
设两个数组L[i],R[i]分别表示到当前位置i时左边取n/2个数的和的最小值和右边取n/2个数的和的最小值,也就是说,答案就是当L[i]+R[i]+i的学费<=F成立的i的最大值。
此处堆的性质:大根堆(用于更新堆中的最大值),保证堆中恒有n/2个值,而且到当前位置时它们的和一定是最小的。
因为堆中的元素恒为i左侧的n/2个最小值,所以我们在更新时只需判断i处学费是否需要和堆中最大元素进行交换就可以维护堆的性质。
具体堆维护:假设有大根堆D,用Vi表示i的学费,从头开始将前n/2个数先加入堆并累加和sum,然后向后扫到i位置时,先将L[i]=sum,若Vi < D.top()则将sum - =D.top(),sum + =Vi,并将D.pop(),D.push(Vi)。
然后更新R[]时倒着扫一遍,步骤同上。
一个优化的地方:在进行堆处理之前先按每个人的成绩排序,最后枚举的过程中直接从最大的成绩开始枚举,找到一个满足L[i]+R[i]+Vi<=F的直接输出。
参考代码:
#include<cstdio>
#include<queue>
#include<algorithm>
#define INF 200000000
using namespace std;
struct re{
int w,v;
} a[110000];
priority_queue<int> d;
int L[110000]={0},R[110000]={0};
int comp(const re&,const re&);
int n,m,k;
int main()
{
freopen("finance.in","r",stdin);
freopen("finance.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++) scanf("%d%d",&a[i].w,&a[i].v);
sort(a+1,a+m+1,comp);
int min=0;
for(int i=1;i<=m;i++) //更新L[ ]数组
{
if(i<=n/2) {d.push(a[i].v);min+=a[i].v;continue;}
L[i]=min;
if(a[i].v<d.top())
{
min-=d.top();
d.pop();
d.push(a[i].v);
min+=a[i].v;
}
}
while(!d.empty()) d.pop();
min=0;
for(int i=m;i>=1;i--) //更新R[]数组
{
if(i>m-(n/2)) {d.push(a[i].v);min+=a[i].v;continue;}
R[i]=min;
if(a[i].v<d.top())
{
min-=d.top();
d.pop();
d.push(a[i].v);
min+=a[i].v;
}
}
for(int i=m-(n/2);i>n/2;i--) //枚举过程
if(L[i]+R[i]+a[i].v<=k)
{
printf("%d",a[i].w);
return 0;
}
printf("-1");
return 0;
}
int comp(const re &a,const re&b)
{
return a.w<b.w;
}