Week4 作业 C - 中位数 POJ Founder Monthly Contest – 2008.04.13, Lei Tao

题目描述:

ZJM拿到了一个神奇的数列X( 𝑋𝑖 ≤ 109 ),里面有n( 𝑛 ≤ 100000 ) 个数。为了研究宇宙的真相,他又构造了一个神奇的数列B,定 义B={ |𝑋𝑖 − 𝑋𝑗| , ∀ 1 ≤ 𝑖 < 𝑗 ≤ 𝑛}。现在ZJM想知道数列B的中位数是什么。当数列长度为偶数时,定义它的中位数为第[长度/2] 个。

思路:

很显然,如果枚举构造数列B,就需要n^2的复杂度,不现实;

回想一下中位数的定义,其一定是在有序序列中,才能在中间位置找到;

则比中位数大的数名次比大,比中位数小的数名次小,则可以二分中位数;

如何确定名次?设中位数是P,如果将X从小到大排列可以去绝对值, 那么计算𝑋𝑗 − 𝑋𝑖 ≤ 𝑃的二元组对数即可,移项可得𝑋𝑗 ≤ 𝑋𝑖 + 𝑃, 𝑖< 𝑗 枚举下标i然后计算满足条件的下标j的个数,

计算下标j的个数时,因为X已经有序,所以也可以二分。

所以,二分P,枚举i,二分有序数组X,找P的名次能大于等于中位数名次的下界。

这里说一下为什么找下界,因为二分出来的P,可能不在序列B中;假设这一步二分使P的名次小于中位数了,则上一步的P就是中位数;

代码:

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;
int p,p_num;    //表示中位数p和p的名次p_num
int n,v[100005];
int find()
{
    int ans=0;
    for(int i=0;i<n-1;i++)
    {
        int x=v[i]+p;
        int l=0,r=n-1,num=0; //在有序数组v中,找最后一个小于等于x的数的序号num,num一定大于等于i 
        while(l<=r)
        {
            int mid=(l+r)/2;
            if(v[mid]<=x) num=mid,l=mid+1;
            else if(v[mid]>x) r=mid-1;
        }
        ans+=num-i;
    }    
    return ans;
}
int bisect_p()
{
    int l=0,r=v[n-1]-v[0],ans=-1;    
    int exp_num=( n*(n-1)/2 + 1 )/2;    //理想的p的名次 
    while(l<=r)
    {
        p=(l+r)/2;
        p_num=find();   //找有多少个j满足Xj<=Xi+P
//        if(p_num==exp_num) return p;    //虽然这个p满足名次,但是这个p可能不在序列B中 
        if(p_num>=exp_num) ans=p,r=p-1; 
//        else if(p_num>exp_num) r=p-1;
        else l=p+1; 
    }
    return ans;
}
int main()
{
    while( scanf("%d",&n)==1 )
    {
        memset(v,0,sizeof(v));
        for(int i=0;i<n;i++)
            scanf("%d",v+i);
        sort(v,v+n);     
        //二分P  其中要求P必须是在序列B中出现过 
        cout<<bisect_p()<<endl; 
    } 
    return 0;
} 

总结:

第一次真正接触二分,虽然说单调就可以二分,但是具体的感觉不是很会用,我觉得我还要多加练习。

posted @ 2020-03-20 22:24  菜鸡今天学习了吗  阅读(121)  评论(0编辑  收藏  举报