题解 -- P4952 [USACO04MAR] Financial Aid

题解 – # 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值