题解 – # P4952 [USACO04MAR] Financial Aid
本题需要用到高级的大根堆来进行优化,且听我一一道来
一句话题意:求在给定的c个奶牛中寻找n个奶牛,使得他们的总花费在f以内并且成绩的中位数最大
注:以下提到的中位数均为题目中定义的中位数
这题的限制条件是花费,那么我们不妨先按照成绩来排个序,最后按成绩从大到小枚举,第一个满足条件的必定是最优的,都不满足条件就是无解(输出-1)
那么现在看如何选奶牛。根据中位数的定义,对于n(n为奇数)个数的中位数k,在这n个数中比k小的有n / 2个,比k大的也有n / 2个。
我们定义对于每一个中位数的下标 i , l[i] 表示在第 i 个数的左边的的最少开销,r[i]表示在第i个数右边的最少开销,那么第i个数的花费就是:
l [ i ] + r [ i ] + a [ i ] . m o n e y l[i] + r[i] + a[i].money l[i]+r[i]+a[i].money
不够清楚?举个栗子:设有3(n = 3)个奶牛,他们的奖学金开销分别为a[1]:10 a[2]:20 a[3]:30那么中位数的下标为2,对应奖学金为a[2] = 20,那么他左边有1个数,为a[2 - int(n / 2)] = a[1] = 10 = l[i],他的右边有一个数为a[2 + int(n / 2)] = a[3] = r[i],那么这三个数的总和为l[i] + r[i] + a[i].money = 10 + 30 + 20 = 60
那么我们现在要求以第i个数为中位数需要的最小开销就可以求了,那现在的问题是:怎样求l[i]和r[i]?我们先看求左边的开销,(求右边的开销和求左边的开销是做法相同的)。首先想到的是:遍历在i左边的所有元素,如果当前元素比当前已选中的元素中最大的更优(开销更少),那就把已选中的元素中最大的踢飞,把更优的元素纳入囊中。这样的话每加一个元素就要sort一遍,时间复杂度慢的一笔。有没有一个数据结构能够保持里面的数是有序的呢?这个时候聪明的你想到了堆。的确,堆好像完美契合我们的要求。不过还要注意,这里查询的是已选的最大的元素,所以使用保持堆内元素单调不上升的大根堆,用STL实现需要头文件queue,c++中是
priority_queue<int, vector<int>, less<int>>
那么我们的思路明了了。因为我们只能选n / 2头奶牛,所以先往堆中推入n / 2个元素,然后再看其他可选的元素是否更优。如果更优,那么加进来。
最后贴上代码
#include <bits/stdc++.h>
using namespace std;
int n, c, f;
int l[100010] = {0}, r[1000010] = {0};
int sum;
struct node{
int mark, money;
}a[100010];
bool cmp(node a, node b){
return a.mark < b.mark;
}
int main(){
cin >> n >> c >> f;
for(int i = 1; i <= c; i ++ ){
cin >> a[i].mark >> a[i].money;
}
sort(a + 1, a + c + 1, cmp);
priority_queue<int, vector<int>, less<int>> q;
for(int i = 1; i <= n / 2; i ++ ){ //先推入n / 2 个元素
q.push(a[i].money);
sum += a[i].money; //sum为最优要花费的价钱
}
for(int i = n / 2 + 1; i <= c - n / 2; i ++ ){ //再将方案更新为最优解
l[i] = sum;
if(q.top() > a[i].money){ //如果开销更少
sum -= q.top();
q.pop();
q.push(a[i].money);
sum += a[i].money;
}
}
while(!q.empty()) q.pop(); //为下一次求r[]做准备
/*********************************************/
sum = 0;
for(int i = c; i >= c - n / 2 + 1; i -- ){
q.push(a[i].money);
sum += a[i].money;
}
for(int i = c - n / 2; i >= n / 2 + 1; i -- ){
r[i] = sum;
if(q.top() > a[i].money){
sum -= q.top();
q.pop();
q.push(a[i].money);
sum += a[i].money;
}
}
for(int i = c - n / 2; i >= n / 2 + 1; i -- ){
if(l[i] + r[i] + a[i].money <= f){
cout << a[i].mark;
return 0;
}
}
cout << "-1";
return 0;
}
大家自己写的时候一定要注意循环变量的值,非常容易出错
就是这样!qaq

205

被折叠的 条评论
为什么被折叠?



