1. 程式人生 > >分治法求平面最近點對

分治法求平面最近點對

題意

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;
}

總結

解法還是很妙的,主要是對求解過程可行性剪枝,這種分治思想也很值得學習。