题意:

给定一个数组A【n】,然后构造出数组B={| X[j] - X[i] |,其中1 <= i < j <=n},求出B数组中的中位数,其中中位数为位数即为排序之后 (len+1)/2 位置对应的数字,’/'为向下取整。

input:

多组输入,每次输入一个 N,表示有 N 个数,之后输入这个数组中的数据, 其中X[i]<= 1e9 , 3 <= n <= 1e5 。

output:

输出各个新数组中的中位数。

思路:

一:暴力算法,将新数组求出来后,取中间的数,n²。
二:二分答案,先将初始数组中的数据进行排序,然后用中位数最大不会超过a[n]-a[1],即中位数的范围为[0 , a[n]-a[1]]。在这其中二分中位数p,其中是否为中位数可以通过得到p的名次来判断是否为中位数。将数组排序后,考虑到绝对值后,即新数组中的每个数为X[j]-X[i],其中j>i。即有判断使得(X[j]-X[i])<= P的(i , j )数对的个数。进行变形后即有X[j]<=X[i] + P。其中可以通过枚举i,再通过二分j,即从i+1到数组长度n得到,得到最后的X[j]的位置,如果有这样的j,其数量则为j-i,依次加上所有这些数量,即可得到总的名次,再根据这个名次的大小与中位数名次的大小进行比较,如果名次大于等于,则向左二分(此处注意名次应该,有相同的时候应该向左查找才是名次)。否则向右二分,重新得到P的值的时候,继续下去,最后二分结束的时候,p可能在l处,也可能在r处,故进行判断输出,如果r的名次为中位数的名次,则输出r,否则输出l。

总结:

很头疼的二分算法,首先要注意二分p的时候,是二分数组中的数据,也就是0到a[n]-a[1]。然后二分j的时候,二分的是是索引。没得到一个二分的P的时候,进行名次判断。然后就是二分中各处的符号,如=该加在<还是>上,细节真的超级多,调到死亡。然后就是在查找j的时候,需要初始化一个标志位,因为有可能在某个i的时候,不存在j。(一开始想着优化在第一遍的时候从j=2到n开始二分,然后得到的j的位置,可以挡作为下次的l,来缩短二分区间,使得下一个的名次为j-i,然后错了的原因可能就是这样的j不存在的时候,仍然加上了)。

代码:

#include <stdio.h>
#include <algorithm>
using namespace std;

int a[100100];
int n,len;  //起始数组的数据数量 


//计算名次 
int f(int p){
	int m=0; //记录名次 
	
	for(int i=1; i<=n; i++){  //二分计算名次 
		int l=i+1,r=n,mid;
		int j=0;     //记录当前的位置
		int pos=a[i]+p;   //查找能使aj<ai+p(即为pos的值)最后一个位置
		 
		while(l<=r){
			mid=(l+r)/2;
			if(a[mid]<=pos){
				j=mid;
				l=mid+1;
			}else{
				r=mid-1;
			} 	
		} 
		if(j!=0) m+=j-i;
	}
	return m;
}
int main(){
	while(scanf("%d",&n)!=EOF){	
		for(int i=1; i<n+1; i++){
			scanf("%d",&a[i]);
		}
		sort(a+1,a+n+1);
		int l=0, r=a[n]-a[1];   //左右
	
		len=(n*(n-1)/2+1)/2;   //中位数的名次 
		 
		while(l<=r){ //不是中位数 
			int p=(l+r)/2;
			if(f(p)>=len){ //p向左二分 
				r=p-1;				
			}else{  //p向右二分 
				l=p+1;	
			}		
		}
		if(f(r)==len)  printf("%d\n",r);
		else printf("%d\n",l); 		
	}

	return 0;
}