分治法求平面最近点对

题意

Here

思考

之前考分治的时候有一道题,要用到 \(O(nlogn)\) 求平面最近点对,然而当时我不会……现在写篇博客回顾一下。

平面上 \(n\) 个点,让我们求最近点对,最朴素的想法是枚举,复杂度 \(O(n^2)\)

这样是显然过不了 \(1e5\) 的数据的,同时我们也发现对于一个点而言,我们计算了许多无用的情况,如何优化?

分治思路:
首先我们将所有点按横坐标排序,选择中位点的横坐标为轴,将整个平面分成两半,那么答案可以变为:

\(min(\) 左半平面上点对的最近距离,右半平面上点对的最近距离,左半和右半平面各选一个点组成的最短距离 \()\)

重点的优化则在求第三个最近距离上,我们设左右半边点对距离的最小值为 \(d\),那么如果两点横坐标绝对值超过 \(d\) 我们就可以不计算了,纵坐标也同理。具体实现则是在分治内再排序一遍,总体复杂度 \(O(nlog^2n)\),如果排序的时候使用归并排序可以将复杂度降为 \(O(nlogn)\)

代码

#include<bits/stdc++.h>
using namespace std;
typedef double D;
const int N = 200020;
int tmp[N], n;
struct node{
    D x, y;
}P[N];
D sqr(D x){
    return x * x;
}
D calc(int a, int b){
    return sqrt( sqr(P[a].x - P[b].x) + sqr(P[a].y - P[b].y) );
}
bool cmp(node a, node b){
    return a.x == b.x ? a.y < b.y : a.x < b.x;
}
bool cmp2(int x, int y){
    return P[x].y < P[y].y;
}
D solve(int l, int r){
    int cnt = 0; double d = 10000000;
    if(l == r) return 100000000;
    if(l + 1 == r) return calc(l, r);
    int mid = (l + r) >> 1;
    d = min( solve(l, mid), d );
    d = min( solve(mid+1, r), d);
    for(int i=l; i<=r; i++){
        if( fabs(P[mid].x - P[i].x) <= d){
            tmp[++cnt] = i;
        }
    }
    sort(tmp+1, tmp+cnt+1, cmp2);//这里的sort复杂度会多一个log
    for(int i=1; i<=cnt; i++){
        for(int j=i+1; j<=cnt && P[tmp[j]].y - P[tmp[i]].y < d; j++){
            double d1 = calc(tmp[i], tmp[j]);
            d = min(d, d1);
        }
    }
    return d;
}
int main(){
    cin >> n;
    for(int i=1; i<=n; i++){
        cin >> P[i].x >> P[i].y;
    }
    sort(P+1, P+n+1, cmp);
    cout << fixed << setprecision(4) << solve(1, n);
    return 0;
}

总结

解法还是很妙的,主要是对求解过程可行性剪枝,这种分治思想也很值得学习。

猜你喜欢

转载自www.cnblogs.com/alecli/p/9917903.html
今日推荐