差值中位数(二分答案+判定中位数)

题意:求n个数两两之间的差值的中位数.

分析:首先(n^2)暴力不难想到吧,两层循环求出所有的差值(一共n*(n-1)/2个),存入一个数组中,然后sort排序,最后直接输出答案.考虑如何美妙地切掉这题?

基本思路是二分答案.为什么可以二分答案,等下讲如何判定当前二分的答案是否成立的时候,就自然而然明白了.我们直接二分中位数mid的值,那么如何判断我们当前二分的这个中位数mid是否成立呢?

我们对原序列sort排序,然后for循环枚举每一个数a[i],找到第一个x,使得a[x]-a[i]>mid,然后y+=n-x+1,即计算对于当前的a[i],有多少个数与它的差值大于mid.如果y大于tot=n*(n-1)/2的一半,说明此次mid大了,向更小的mid继续二分.如果y小于tot,则说明此次mid小了,向更大的mid继续二分(从这里应该可以看出答案具有单调性吧,所以可以二分答案).

值得一提的是,对于每一个a[i],找到第一个x,使得a[x]-a[i]>mid,我们没有必要每一次都让x从1开始,一个一个到n这样去找(这样的话,时间复杂度(n^2log_n)),因为我们的序列a是单调递增的,所以每一次i++之后,a[i]都是在递增的,所以a[x]-a[i]一定又会小于(最多等于)mid,总之,x是不可能需要往回走的,所以每一次a[i]改变之后,x是不需要重置为1的.

有趣之处在于,对于一个差值数列,如果数量是奇数,中位数就在tot/2+1的下标上,而如果数量是偶数,中位数则是tot/2和tot/2+1两个下标上的数的平均值.按照上述二分方法,我们二分到的数一定是差值数列中已经存在的数,所以在遇到偶数情况时,是可能出错的(因为偶数情况时,中位数可能不在原差值数列中),于是我们需要两次二分,然后取平均值.两次二分,稍稍改动一个边界条件即可.

LL a[50005];
int main(){
    LL n=read(),ans1,ans2;
    LL tot=n*(n-1)/2;//差值序列的个数
    for(int i=1;i<=n;i++)a[i]=read();
    sort(a+1,a+n+1);//将原序列sort排序
    LL l=0,r=a[n]-a[1];//注意l的初始值
    while(l<=r){
		LL mid=(l+r)>>1,x=1,y=0;
		for(int i=1;i<=n;i++){
	    	while(x<=n&&a[x]-a[i]<=mid)x++;
	    	y+=n-x+1;
		}
		if(y*2<tot){
	    	ans1=mid;
	    	r=mid-1;
		}
		else l=mid+1;
    }
    l=0,r=a[n]-a[1];
    while(l<=r){
		LL mid=(l+r)>>1,x=1,y=0;
		for(int i=1;i<=n;i++){
	    	while(x<=n&&a[x]-a[i]<=mid)x++;
	    	y+=n-x+1;
		}
		if(y*2<=tot){//唯一的不同之处
	    	ans2=mid;
	    	r=mid-1;
		}
		else l=mid+1;
    }
    printf("%lld
",(ans1+ans2)/2);
    return 0;
}

原文地址:https://www.cnblogs.com/PPXppx/p/10362354.html