Usaco Financial Aid,2004 Mar 赞助学费

来源:互联网 发布:网络收发器 编辑:程序博客网 时间:2024/04/29 10:31

分析:

很巧妙的一个堆维护+枚举。

在对成绩排序后,我们无需知道一个成绩中位数左右两侧的选择具体是谁,只需知道左右两侧各取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 200000000using 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;}


2 0
原创粉丝点击