二分答案求中位数

题目
TT 的神秘礼物

TT 是一位重度爱猫人士,每日沉溺于 B 站上的猫咪频道。
有一天,TT 的好友 ZJM 决定交给 TT 一个难题,如果 TT 能够解决这个难题,ZJM 就会买一只可爱猫咪送给 TT。
任务内容是,给定一个 N 个数的数组 cat[i],并用这个数组生成一个新数组 ans[i]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(cat[i] - cat[j]),1 <= i < j <= N。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。
TT 非常想得到那只可爱的猫咪,你能帮帮他吗?

Input
多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5

Output
输出新数组 ans 的中位数

Sample input
4
1 3 2 4
3
1 10 2

Sample output
1
8

解题思路
二分答案比较抽象,理解起来比较困难。
我们先把数组给排好序 , 这样就便于去掉绝对值。
然后我们知道, 每两个数都可以有一个差值,可以成为新数组的一部分,所以我们就算出 新数组一共有 ( n-1+1 ) * ( n-1) / 2 个,即 n* (n-1) /2。
所以我们就可以得到新数组中中位数的位置 N = ( n*(n-1)/2 + 1)/2 ;
然后,我们把答案的区间初始化为 [ 0 , max ] ,其中 max = cat[n] - cat[1] ;
我们二分这个区间,不断地缩小区间,直到 L > R ,便得到我们要求的中位数。
那么这时,困难在于二分时如何进行判断?
我们的方法是, 定义一个juge ( mid ) 函数 ,其中mid = 区间的中点。
[ 我们需要判断 cat[i] - cat[j] <= mid 的个数,即为 mid 在 新数组里面的位置,当个数大于 N 时,它便在中位数右边,小于N,则在中位数左边 ,我们用sum 记录这个个数]
我们在这个函数里面循环 cat数组 ,然后计算,返回一个bool值。

代码实现

#include <cstdio>
#include <algorithm>
using namespace std;
long long cat[100005];
long long n;
long long N;

bool juge(long long x){
	long long sum=0;
    for( int i=1;i<=n;i++)
    {
    	sum+=lower_bound(cat+i,cat+n+1,cat[i]+x+1)-(cat+i)-1;  //找到最后一个小于cat[i]+x+1的地址
    	if( sum >=N )                                //然后减去cat+i的地址,得到的是小于cat[i]+x+1
                                                   //的个数,将他们都加起来,就得到mid在新数组中的位置
    	{
    		return 1;
		}
	}
	return 0;
}
int main(){
    while (scanf( "%lld",&n)!=EOF ){
    	if( n == 0) {printf("0\n");  continue;}	
    	for( int i=1;i<=n;i++)
    		scanf("%lld",&cat[i]);		
    	//for( int i=0;i<n;i++)
    	//	printf("%lld ",cat[i]);		
    	N = ( n*(n-1)/2 + 1)/2;	
    	sort(cat+1,cat+n+1); 
    //	for( int i=0;i<n;i++)
    //		printf("%lld ",cat[i]);
    //		printf(" * ");
    	long long l = 0, r = cat[n]-cat[1];
    //	printf("%lld ",r);
    //	printf(" **\n ");
    	while( l < r )
    	{  	    
    		long long mid = (l+r) >> 1;
    //		printf("%lld ",mid);
    //		printf(" ***\n ");
    		if( juge(mid) ) r= mid;
    		else l = mid + 1;
		}
    	printf("%lld\n",l);
	} 
    return 0;
}
/*
template<class T>
T find_l (T x,T g[],T n,T i)  //找到最后一个小于x的索引 
{
	T l = i,r = n-1,ans = -1;
	while( l <= r ){
		T mid = (l+r) >> 1;
		if( g[mid] <= x)
		{
		//	cout<<"l"<<endl;
			ans = mid;
			l = mid +1;
		}
		else   r = mid -1;
	}
	return ans;
}
*/

小结
我在juge函数里借鉴了网上的方法,用了lower_bound()函数,我看了这个函数的源码,本质上也是二分的,用在这里很合适。但是如果不想用这个函数的话,也可以自己写二分, find_l()函数,返回最后一个小于x的值的位置,再减去 i ,就可以得到循环到 i 的符合要求的个数,然后加起来得到sum,也可以求出mid 的名次 ,最后便可以求得中位数。 【本质上是不断缩小区间,得到值】我觉得答案二分好难啊,刚开始坐在电脑前坐了一个下午,都没想明白为什么可以二分答案 , 后来才想明白二分的是答案的区间, 去 逼近我们要求的那个答案 。

发布了31 篇原创文章 · 获赞 1 · 访问量 1053

猜你喜欢

转载自blog.csdn.net/Hanpi_learnc/article/details/104953856