题目描述:
给定一个 N 个数的数组 cat[i],并用这个数组生成一个新数组 ans[i]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(cat[i] - cat[j]),1 <= i < j <= N。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。
输入:
多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5 。
Sample Inpout:
4
1 3 2 4
3
1 10 2
输出:
输出新数组 ans 的中位数
Sample Output:
1
8
思路:
对于输入的cat数组,我们可以知道ans数组的大小,因此我们也可以知道中位数的位次,即小于中位数的数的个数,给定一个数P,若其位次小于中位数,则他小于中位数,若位次大于中位数,则其大于中位数。我们可以看出答案具有单调性,因此可以考虑二分的做法。我们将cat数组排序,则ans数组可由i<j,cat[j]-cat[i]求得,达到去绝对值的目的,同时我们也可求出中位数的大致范围,即数组中最后一个数减第一个数就是其最大值,且一定大于零,据此,我们也可对中位数范围进行二分,然后根据二分得到的数,再判断其与中位数的大小,若大于中位数,则二分前半部分,否则二分后半部分。
此时,剩下的问题就是给定二分得到的数据P后如何判断他与中位数的大小,即判断小于他的数有多少,因为ans[x]<P,即cat[j]-cat[i]<p(i<j)即cat[i]+P>cat[j],据此,我们遍历i,求取j的范围,可得到小于P的数据个数记作count,同时我们也求取数据等于P的个数记作temp。
记中位数位次为target,若count<target&&count+temp>=target则P即为中位数,否则若count+temp<target则P小于中位数,继续二分中位数范围后半部分,再则P大于中位数,继续二分前半部分。直到得到中位数。
代码:
#include <iostream>
#include<algorithm>
using namespace std;
const int size=1e5+10;
int number[size];
int end(int x,int left,int n)
{//小于x的最后一个数
int ans=-1;
int ll=left+1,rr=n-1;
while(ll<=rr)
{
int mid=(ll+rr)>>1;
if(number[mid]<x)
{
ans=mid;
ll=mid+1;
}
else
rr=mid-1;
}
return ans;
}
int getans(int n)
{
int right=number[n-1]-number[0];
int left=0;
int sum=0;
for(int i=1;i<n;i++)
sum+=i;
int target=(sum+1)/2;
while(left<=right)
{
int mid=(left+right)>>1;
int count=0,temp=0,ans=0;
for(int i=0;i<n;i++)
{
int en=end((mid+number[i]),i,n);
if(en!=-1)
{
count+=(en-i);
for(int j=en+1;j<n&&number[j]==(number[i]+mid);j++)
temp++;
}
else
for(int j=i+1;j<n&&number[j]==(number[i]+mid);j++)
temp++;
}
if(count<target&&(count+temp)>=target)
return mid;
else if(count+temp<target)
left=mid+1;
else
right=mid-1;
}
}
int main(int argc, char** argv) {
int n;
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<n;i++)
scanf("%d",&number[i]);
sort(number,number+n);
int ans=getans(n);
printf("%d\n",ans);
}
return 0;
}