题意:
给定一个数组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;
}