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; }
总结:
第一次真正接触二分,虽然说单调就可以二分,但是具体的感觉不是很会用,我觉得我还要多加练习。