【书上讲解】平面上最近点对问题

给你二维平面上的n个点,让你求出其中最近的点对。

【题解】


这是一个分治的问题。
可以这样做:
首先将n个点按照x升序排。
然后将l..r这个区间内的点分成
l..mid和mid+1..r两个部分递归求解。
分别求出这两段里面的点的最近点对的距离d1和d2
然后令d = min(d1,d2)
这是最后答案的点都在其中一边的情况。
现在考虑最后答案的点对分别在左区间p1和右区间p2的情况。
首先可以将左区间和右区间中点的横坐标和分割线距离超过d的点去掉。
然后对于每一个p1,在其右侧画一个长d,高2d的矩形。
就会发现那个矩形内最多只会有6个右区间中的点(鸽巢原理)
所以我们只需要将l..r这个区间内的点按照y轴升序排一下序就好。
然后对于每个点(x,y)检查y坐标的范围在(y-d,y+d)的其他点就好.
但这样排序的话,时间复杂度是nlognlogn级别的。
我们可以在进行答案的合并的时候,顺便对每个子区间做一下归并排序(按照y轴).
然后归并排序的时候,对于左区间的点,顺便遍历和它的y坐标差的绝对值小于d的其他点即可
这样时间复杂就是n*logn级别的了

【代码】

#include <cstdio>
#include <cmath>
using namespace std;

const int N = 1e5;
const int oo = 1e9;

struct abc{
    int x,y;
};

int n;
abc a[N+10];
abc b[N+10];

int sqr(int x){return x*x;}

int dis(abc a,abc b){
    return sqr(a.x-b.x)+sqr(a.y-b.y);
}

int min(int x,int y){
    if (x<y) return x;else return y;
}

int divide(int l,int r){
    if (l>=r) return oo;
    int mid = (l+r)>>1;abc mp = a[mid];
    int d = divide(l,mid);d = min(d,divide(mid+1,r));
    int i = l,j = mid + 1,k = l;
    while (i<=mid || j<=r){
        while (j<=r && (i>mid || a[j].y<=a[i].y)) b[k++] = a[j++];
        if (i<=mid && mp.x-a[i].x<d){//a[i]是分割线左边的点
            //此时a[mid+1..j-1]全都小于等于a[i]的纵坐标
            //a[j..r]全都大于a[i]的纵坐标。(且这两段的点都是分割线右边的点)
            for (int l = j-1;l>=mid+1;l--){
                if (sqr(a[i].y-a[l].y)>d) break;
                int temp = dis(a[l],a[i]);
                d = min(temp,d);
            }
            for (int l = j;l<=r;l++){
                if (sqr(a[l].y-a[i].y)>d) break;
                int temp = dis(a[l],a[i]);
                d = min(temp,d);
            }
        }
        if (i<=mid) b[k++] = a[i++];
    }
    for (int i = l;i<= r;i++) a[i] = b[i];
    return d;
}

void _swap(abc *x,abc *y){
    abc t = *x;
    *x = *y;
    *y = t;
}

void kp(abc a[],int l,int r){
    abc temp = a[l];
    int i = l,j = r+1;
    while(true){
        while (a[++i].x<temp.x && i<r);
        while (a[--j].x>temp.x);
        if (i>=j) break;
        _swap(&a[i],&a[j]);
    }
    a[l] = a[j];
    a[j] = temp;
    if (l<j) kp(a,l,j-1);
    if (j<r) kp(a,j+1,r);
}

int main(){
    scanf("%d",&n);
    for (int i = 1;i <= n;i++) scanf("%d%d",&a[i].x,&a[i].y);
    kp(a,1,n);
    double ans = sqrt(1.0*divide(1,n));
    printf("%.3f\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/AWCXV/p/11622575.html