题目
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
return 1; //的个数,将他们都加起来,就得到mid在新数组中的位置
}
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]);
N = ( n*(n-1)/2 + 1)/2;
sort(cat+1,cat+n+1);
long long l = 0, r = cat[n]-cat[1];
while( l < r )
{
long long mid = (l+r) >> 1;
if( juge(mid) ) r= mid;
else l = mid + 1;
}
printf("%lld\n",l);
}
return 0;
}
小结
我在juge函数里借鉴了网上的方法,用了lower_bound()函数,我看了这个函数的源码,本质上也是二分的,用在这里很合适。但是如果不想用这个函数的话,也可以自己写二分, find_l()函数,返回最后一个小于x的值的位置,再减去 i ,就可以得到循环到 i 的符合要求的个数,然后加起来得到sum,也可以求出mid 的名次 ,最后便可以求得中位数。 【本质上是不断缩小区间,得到值】我觉得答案二分好难啊,刚开始坐在电脑前坐了一个下午,都没想明白为什么可以二分答案 , 后来才想明白二分的是答案的区间, 去 逼近我们要求的那个答案 。