题意:
给定一个 N 个数的数组 cat[i],并用这个数组生成一个新数组 ans[i]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(cat[i] - cat[j]),1 <= i < j <= N。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。
输入:
多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5
输出:
输出新数组 ans 的中位数
输入样例:
4
1 3 2 4
3
1 10 2
输出样例:
1
8
解题思路:
10^5 的数据量,n^2 的复杂度计算出新的数组不可取。我们选择不计算新的数组具体是怎么样的,转而利用二分的思想求出某一个数然后判断是不是中位数,不停地二分逼近最终得到具体的中位数是多少。新数列的最小值不会小于0,对原数列按从小到大排序有利于去掉绝对值符号,新数组的最大值为cat的最后一个减去cat的第一个。判断一个数x在新数组中的位置的方法是计算所有小于等于x的数的个数,即xj-xi<=x(j>i)这样的数对的个数,即xj<=xi+x(j>i),我们对i进行循环枚举,得到xi+x的值,然后判断在原数列中最后一个小于等于xi+x的xj的位置,然后这样的数对的个数需要增加的值是这个xj的位置减去当前枚举的xi的下标i。我们用刚刚提到的0和cat的最后一个减去cat的第一个的最大值来开始二分,得到每一个mid值的位置pos,pos>=中位数应该的position((n*(n-1)+2)/4)时,把mid-1赋给r,否则mid+1赋给l,这样可以找到pos>=中位数应该的position((n*(n-1)+2)/4)的最小的数,这个数就一定是我们要找的中位数。
注意事项:
1、用cin读入比scanf更快。
2、不能在(pos=pos_of_mid)时就停止二分,要在l>r时停止二分,因为即使pos==pos_of_mid时得到的数可能仍比中位数大,我们需要找到pos>=中位数应该的position((n*(n-1)+2)/4)的最小的数才行。
总结:
一道较有难度的二分思想的题目,需要用到多次二分,求一个数在新数列中的位置时使用二分,整个题目的求解思路也是以二分为基础来寻找中位数的,这种不求出具体数组但通过二分寻求中位数的思想比较重要。求一个数在新数列中的位置时所使用的单次枚举的思路也能有效优化降低时间复杂性。
参考代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
#include <iomanip>
#include <map>