一、题目大意
有1-n个数字x[i](1=<i<=N),它们通过(x[j]-x[i])(1<=i<j<=n)组成了一个有n*(n-1)/2个元素的数列,要求我们计算出其中的中位数(这里的中位数的顺序是数列长度(m/2)向上取整)
二、解题思路
题目的数量级是1e5,暴力枚举超时,用二分才能解决,其中二分的核心依据如下。
对数列中的元素进行二分,设二分的值为mid
每次找出数列中小于mid的元素数量,判断数列中小于mid的值是否小于一半
如果数列中小于mid的元素数量大于等于一半,那么mid取大了,缩小mid
如果小于一半,那么mid取小了,增大mid
直到找到一个临界值limit,使得当mid=limit时
数列中小于mid的元素数量不足一半
当mid=limit+1时
数列中小于mid的元素超过或者等于一半
那么不难看出,这个limit一定是中位数
那么接下来就是如果找到数列中小于mid的元素个数了,这其实也很简单,思路如下
我们首先对所有的元素x进行排序
对于每一个元素x[i],我们找到大于等于x[i]+mid的元素j,对于任意k∈(i,j),一定有
x[k]-x[i]<mid<=x[j]-x[i]
那么这j-i-1个元素(x[k]-x[i],k∈(i,j)),其实都是数列中小于mid的元素
所以我们用i对1到n进行循环,找出每一个元素x[i]对应的j,然后将所有的j-i-1求和
最终和的值就是数列中小于mid的元素
当然了,不是每一次枚举的mid的值都在数列中,但是这不重要,因为最终的临界值limit一定在数列中
因为数列中<limit的元素个数不足一半,小于limit+1的值,超过了数列元素的一半。
所以其他的计算都是用来不断的靠近limit
需要注意的是,在计算a[i]+mid和(left+right)/2时要当心不要溢出,我针对于这一步计算时开了64位整数
然后left的话取-1就好,right就取数组的极差+1(二分算出的mid∈[left+1,right-1])
三、代码
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
int arr[100007], inf = 0x3f3f3f3f, n, k;
void input()
{
for (int i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}
int count = n * (n - 1) / 2;
k = count / 2;
if (count % 2 != 0)
{
k++;
}
sort(arr, arr + n);
arr[n] = inf;
}
bool judge(int mid)
{
ll countLt = 0, countEq = 0;
for (int i = 0; i < n; i++)
{
ll val = arr[i];
val += mid;
int rangeLt;
if (val < inf)
{
rangeLt = lower_bound(arr, arr + n + 1, arr[i] + mid) - arr - i - 1;
}
else
{
rangeLt = n - i - 1;
}
countLt = countLt + rangeLt;
}
return countLt < k;
}
void binarySearch()
{
int left = -1, right = arr[n - 1] - arr[0] + 1;
while (left + 1 < right)
{
ll mid = left;
mid += right;
mid = mid >> 1;
if (judge((int)mid))
{
left = mid;
}
else
{
right = mid;
}
}
printf("%d\n", left);
}
int main()
{
while (~scanf("%d", &n))
{
input();
binarySearch();
}
return 0;
}