Median POJ - 3579 (二分套二分) 详细题解

在这里插入图片描述
题意: 给出n个数x1…xn, 求所有|xi - xj|中中位数的大小, 如果总数m为偶数, 则为(m+1)/2

题解: 这是一道较有难度的二分题目, 对于二分题目我们的思考角度仍然是从原始思路入手, 看看哪里可以优化
将x排序后, 我们首先二分查找中位数k, 范围应该是0-a[n], 这一点不难想到, 同时通过数学知识可知m = (n*(n-1)/2 + 1)/2, 那下一步的问题就是如何检验是否有m个<=k的|xi - xj|
在这里我提供一种思路, 依然是假设+检验的思路, 因为中位数k已经确定(上一层二分中假设), 我们可以枚举j的下标(j >= i), 同时二分查找i的下标, x[i] = x[j]-k, 我们不断累积j-i的和其实就是|xi - xj|的数量, 发现大于m跳出即可
数据较大, 请使用scanf

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define ms(x, n) memset(x,n,sizeof(x));
typedef  long long LL;
const LL maxn = 1e5+10;

int n, x[maxn];
LL m;
int findi(int s, int r)
{   //在[1,r]范围内查找>s的第一个下标
    int l = 1;
    while(l < r){
        int mid = (l+r)>>1;
        if(x[mid]>=s) r = mid;
        else l = mid+1;
    }
    return l;
}
bool check(int k)
{   //检验k为中位数时是否有大于等于m个<=k的|a[i]-a[j]|
    LL sum = 0;
    //查找j, 通过枚举i同时累加|a[i]-a[j]|小于m的数量, >=k则跳出
    for(int j = 1; j <= n; j++){
        sum += (LL)j - findi(x[j]-k, j);
        if(sum >= m) return true;
    }
    return false;
}
int main()
{
    while(scanf("%d",&n)!=EOF){
        for(int i = 1; i <= n; i++)
            scanf("%d",&x[i]);
        m = (LL)(n*(n-1)/2+1)/2; //中位数下标

        sort(x+1, x+1+n);
        //二分查找中位数
        int l = 0, r = x[n];
        while(l < r){
            int mid = (l+r)>>1;
            if(check(mid)) r = mid;
            else l = mid+1;
        }
        printf("%d\n",l);
    }

	return 0;
}

猜你喜欢

转载自blog.csdn.net/a1097304791/article/details/86765110