题意: 给出n个数x1…xn, 求所有|xi - xj|中中位数的大小, 如果总数m为偶数, 则为(m+1)/2
题解: 这是一道较有难度的二分题目, 对于二分题目我们的思考角度仍然是从原始思路入手, 看看哪里可以优化
将x排序后, 我们首先二分查找中位数k, 范围应该是0-a[n], 这一点不难想到, 同时通过数学知识可知m = (n*(n-1)/2 + 1)/2, 那下一步的问题就是如何检验是否有m个<=k的|xi - xj|
在这里我提供一种思路, 依然是假设+检验的思路, 因为中位数k已经确定(上一层二分中假设), 我们可以枚举j的下标(j >= i), 同时二分查找i的下标, x[i] = x[j]-k, 我们不断累积j-i的和其实就是|xi - xj|的数量, 发现大于m跳出即可
数据较大, 请使用scanf
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define ms(x, n) memset(x,n,sizeof(x));
typedef long long LL;
const LL maxn = 1e5+10;
int n, x[maxn];
LL m;
int findi(int s, int r)
{ //在[1,r]范围内查找>s的第一个下标
int l = 1;
while(l < r){
int mid = (l+r)>>1;
if(x[mid]>=s) r = mid;
else l = mid+1;
}
return l;
}
bool check(int k)
{ //检验k为中位数时是否有大于等于m个<=k的|a[i]-a[j]|
LL sum = 0;
//查找j, 通过枚举i同时累加|a[i]-a[j]|小于m的数量, >=k则跳出
for(int j = 1; j <= n; j++){
sum += (LL)j - findi(x[j]-k, j);
if(sum >= m) return true;
}
return false;
}
int main()
{
while(scanf("%d",&n)!=EOF){
for(int i = 1; i <= n; i++)
scanf("%d",&x[i]);
m = (LL)(n*(n-1)/2+1)/2; //中位数下标
sort(x+1, x+1+n);
//二分查找中位数
int l = 0, r = x[n];
while(l < r){
int mid = (l+r)>>1;
if(check(mid)) r = mid;
else l = mid+1;
}
printf("%d\n",l);
}
return 0;
}