挑战程序设计竞赛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;
}

 

  

 

posted @ 2020-02-24 16:32  funforever  阅读(136)  评论(0编辑  收藏  举报