C - Median(二分)

C - Median(二分)


Time limit : 1000ms / Memory limit : 65536 kb
OS : Linux

Problem Statement
Given N numbers, X1, X2, … , XN, let us calculate the difference of every pair of numbers: ∣Xi - Xj∣ (1 ≤ i < j ≤ N). We can get C(N,2) differences through this work, and now your task is to find the median of the differences as quickly as you can!
Note in this problem, the median is defined as the (m/2)-th smallest number if m,the amount of the differences, is even. For example, you have to find the third smallest one in the case of m = 6.

Input
The input consists of several test cases.
In each test case, N will be given in the first line. Then N numbers are given, representing X1, X2, … , XN,
( Xi ≤ 1,000,000,000 ; 3 ≤ N ≤ 1,00,000 )

Output
For each test case, output the median in a separate line.

Sample Input
4
1 3 2 4
3
1 10 2

Sample Output
1
8

题意
给定N个数X1…XN,计算"-两数之差-|绝对值|"的中位数(Median)。

分析
根据组合数学,N个数取2个,总数m为C2n,即m = n*(n-1)/2
根据题目对Median的定义,我们要求出C2n个差里第m/2小的数,即ceil(n*(n-1)/4.0),向上取整。
好,这是从题目里面得出的信息,…貌似并没有什么卵用。

m大概是O(n2)的时间复杂度,一看N的范围,1e5,哦吼,完蛋。
即使一秒能计算1e8个|差|,也大概要等个100s吧。
"害,人生啊。生命在ACM面前,也突然就短暂了呢。"

好了,以上基本是废话,既然我们不能计算所有的差,那怎么才能知道中位数呢?
我们不妨反向思考一下,中位数是一个名次,也就是说只要知道了一个数的名次,就可以判断它是不是中位数,在中位数的左边还是右边。

那好办,蒙特卡洛,蒙呗 ,咳咳,我说的是二分,二分的前提是有序,所以先预处理sort一下
将|两数之差|进行二分,假设它是题目所求量(中位数),然后计算天选之子的名次,以此来靠近真正的中位数,那么左右边界就要是|两数之差|可能的最小、最大值,left=0,right=2e9,差不多,二分很快,不得慌。

那么问题又来了,如何计算一个值在一个不存在的序列中(我们不去计算两两之差)的排名?
刚刚的sort在这里就有大用处了,序列已经有序,比如1 2 3 4,那么1和2 3 4 的差就是递增的,假设这里的1是Xi,我们只要在接下来的Xj中找到大于[天选之子+Xi]的值的下标,减去Xi的下标就是该行对天选之子总排名的贡献了,此过程仍然依赖二分(因为是有序的),可以用upper_bound快速实现。
那么只要依次遍历i从1~N-1即可。

OK,综上,这题其实是双重二分。
"Talk is Cheap. Show me the Code."

#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#include<math.h>
using namespace std;
int a[100005];
int n,Rank;
bool calc(int m);
int main(void)
{
	while (scanf("%d", &n) != EOF)
	{
		for (int i = 0; i < n; i++)
			scanf("%d", &a[i]);
		sort(a, a + n);
		Rank = ceil((double)n * (n - 1.0) / 4.0);//计算中位数排名
		int l = 0, r = 2e9, mid;
		while (l < r)
		{
			mid = (l + r) / 2;
			if (calc(mid))
				r = mid;
			else
				l = mid + 1;
		}
		printf("%d\n", r);
	}
	return 0;
}
bool calc(int m)//1 2 3 4//计算天选之子实际排名,并与中位数排名比较
{
	int sum = 0;
	for (int i = 1; i < n; i++)
		sum += upper_bound(a + i, a + n, a[i - 1] + m) - (a + i);
	return sum >= Rank;
}
发布了4 篇原创文章 · 获赞 1 · 访问量 107

猜你喜欢

转载自blog.csdn.net/qq_33374268/article/details/104347100