挑战程序设计竞赛3.1习题:Moo University - Financial Aid POJ - 2010
(原题见POJ2010)
这道题我之前采用了优先队列+预处理的方法求解(https://www.cnblogs.com/jacobfun/p/12244509.html),现在用二分的办法进行求解。
一开始我很纳闷,采用二分求解本题,如果二分的mid值不符合条件,按照二分右边界应该为mid - 1(我采用前闭后闭的二分),那么如果mid + xxx(xxx大于0)可以呢?(考虑mid不行是因为左边最小加起来大了,mid ~ mid + xxx中有极小值,使得mid + xxx的左边可以满足,那么这样子就矛盾了,怎么能二分呢?(百思不得其姐))后来我仔细看了网上的博客,终于发现了题解与我的想法不同之处——不是不满足条件一定缩小右边界!!!
这道题是这样二分的:先对于分数从小到大排序,我们先在n/2~c - n/2的范围内(因为中位数只能存在于这个范围内,n为录取的奶牛,c为申请的奶牛)取mid,判断是否有在mid左右边各存在n/2个数使得mid的资助费+左右的资助费<=最多拿来做助学金的费用f,我们找出当总助学金小于等于f且中位数是mid时,左右最多有多少个(当然左右的个数不能超过n/2),有以下几种情况:
1.如果左右最大存在的使得总资助费小于f的个数均小于n/2,换言之mid左右都不可能存在有n/2个满足总资助费小于f的,那么不管你把值变成mid的左边(那样因为二分搜索的值在mid左边,那么左边可选的总数(也就是分数比搜索的值小的)是我们之前搜索mid的左边的子集(现在搜索的值的左边分数 < 现在搜索的值的分数 < mid, 所以现在搜索的值的左边的分数集合是mid的左边分数集合的子集),之前mid左边分数的集合都没有满足题意的情况,它的子集更不可能),或者把值变成右边(同左边解释一样),都不可能,所以答案无解直接输出-1
2.如果只满足某一项,比如只有mid左边个数小于n/2,那么范围在(left ~ mid)之间他们的每个值的左边都是mid的左边的子集(而mid的左子集不行),所以也都不行,只能把范围变成mid + 1 ~ right,当只有mid右边的个数小于n/2时,在(mid ~ right)之间每个值的右边的子集都是mid的右边的子集,也都不行,范围就是left ~ mid - 1。
3.如果都满足n/2,那么这就是某一可能值,我们要记录下来。
AC代码:
#include <stdio.h> #include <algorithm> using namespace std; struct Node{ int score; int need; friend bool operator <(Node x, Node y){ return x.score < y.score; } }stu[100005]; struct Nodes{ int score; int need; int id; friend bool operator <(Nodes x, Nodes y){ return x.need < y.need; } }stu2[100005];//为了找到是否存在我们对mid的左右采取取符合条件的最小值,所以要对助学金进行排序,从小到大,这样每次找到的一定是满足条件最小的,如果这样都不能找到符合题意的,那么说明不存在这样的解 int ans;//答案 int n, c, f; int check(int x) { int sum = stu[x].need, l = 0, r = 0;//注意计算总助学金sum别忘了中位数的助学金,l是x(也就是mid)的左边满足sum <= f的最大个数 for(int i = 0; i < c; i++) { if(stu2[i].id < x && stu2[i].need + sum <= f && l < n / 2)//如果这个值使得sum加上后小于等于f,且在x的左边,且左边的值现在还没找到n/2 { l++; sum += stu2[i].need; } else if(stu2[i].id > x && stu2[i].need + sum <= f && r < n / 2) { r++; sum += stu2[i].need; } } if(l < n / 2 && r < n / 2)//情况1 return -1; else if(l < n / 2)//情况2 return 1; else if(r < n / 2) return 2; else//符合题意的解 return 0; } int main(void) { scanf("%d %d %d", &n, &c, &f); for(int i = 0; i < c; i++) { scanf("%d %d", &stu[i].score, &stu[i].need); } sort(stu, stu + c); for(int i = 0; i < c; i++) { stu2[i].id = i;//记录对分数排序的情况下的编号,便于区分是mid的左边还是右边,对于分数相同的,我们也以不同编号进行处理,这样可以考虑到中位数值相同的情况 stu2[i].need = stu[i].need; stu2[i].score = stu[i].score; } sort(stu2, stu2 + c);//对助学金进行排序 int left = n / 2, right = c - n / 2;//前闭后闭 while(left <= right) { int mid = (left + right) / 2; int c = check(mid); if(c == -1) { ans = -1; break; } else if(c == 1) { left = mid + 1; } else if(c == 2) { right = mid - 1; } else { ans = stu[mid].score; left = mid + 1; } } printf("%d\n", ans); return 0; }