POJ 3579 Median 二分

一、题目大意

有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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值