题意: 每头牛有价格和价值两个属性, 有F资金, 从C头牛中选出N头(N为奇数), 求C头牛中最大的价值中位数
题解: 对于这种题, 八成猜也可以猜到就是二分/优先队列了, 我们需要考虑的, 就是从哪个角度入手.
对于中位数这种东西, 排个序, 我们完全是可以二分来求的, 再考虑一下check函数, 因为同时引入了价格和价值两个属性, 所以略微有些麻烦啊. 我们二分价值, 再对做一遍 n 的检验, 中位数左右的总价格和数量是否达标, 应该是会分为四种情况, 再依次判断
具体的看代码吧
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define ms(x, n) memset(x,n,sizeof(x));
typedef long long LL;
const LL maxn = 1e5+10;
struct node{
int s, a, i;
}cow[maxn], aid[maxn];
bool cmp1(node a, node b){ return a.s < b.s;}
bool cmp2(node a, node b){ return a.a < b.a;}
int N, C, F, ans = -1; //M为中位下标, cntI为中位前后的数量
int main()
{
scanf("%d%d%d",&N,&C,&F);
for(int i = 1; i <= C; i++)
scanf("%d%d",&cow[i].s,&cow[i].a);
sort(cow+1, cow+1+C, cmp1);
for(int i = 1; i <= C; i++)
cow[i].i = i;
memcpy(aid+1, cow+1, sizeof(node)*C);
sort(aid+1, aid+1+C, cmp2);
int l = 1, r = C;
//二分查找值
while(l < r){
int mid = (l+r)/2;
//计算以cow[mid]为中点时, 左右的总价格, 数量
int left = 0, right = 0, sum = cow[mid].a, half = N/2;
for(int i = 1; i <= C; i++){
if(aid[i].i<mid && left<half && sum+aid[i].a<=F)
sum+=aid[i].a, ++left;
else if(aid[i].i>mid && right<half && sum+aid[i].a<=F)
sum+=aid[i].a, ++right;
}
if(left<half && right<half) //情况1: 数量不够, 无法取得中位数
break;
else if(left<half && right>=half) //情况2: 偏左
l = mid+1;
else if(left>=half && right<half) //情况3: 偏右
r = mid;
else if(left>=half && right>=half) //情况4: 可以取得中位数, 试试更大的
ans = max(cow[mid].s, ans), l = mid+1;
}
printf("%d\n",ans);
return 0;
}
再说一下这道题的优先队列思路
同样先排序, 考虑每个奶牛作为中位数时,比它分数低(前面的)的那群牛的学费总和lower_i,后面的总和upper_i。然后从分数高往分数低扫描,满足aid_i + lower_i + upper_i <= F的第一个解就是最优解。
代码暂且省略啦
通过这道题我们可以发现, 很多时候优先队列/二分这种logn的优化方法是可以互换的