[題解] [洛谷P7883] 平面最近點對(加強版)

wxy3265發表於2024-04-12

[洛谷P1429] 平面最近點對(加強版)

題目描述

給定平面上的 \(n\) 個點,求其中距離最小的兩個點之間的距離。

輸入格式

第一行: \(n\) ,保證 \(2\leq n \leq 200000\)

接下來 \(n\) 行,每行兩個實數: \(x, y\),表示一個點的橫座標和縱座標,中間用一個空格隔開。

輸出格式

僅一行,一個實數,表示最短距離,精確到小數點後4位。

題解

如果暴力列舉,則複雜度為 \(O(n^2)\),顯然無法透過此題。

考慮分治,將點按照橫座標排序編號後二分割槽間求解。邊界條件是兩個編號相同的點或者編號相鄰的點,都可以方便的得到它們的距離。問題的關鍵是合併兩個區間時如何計算跨區間的點之間的距離。所謂跨區間,實際上是分佈在分割線兩側的點,即 \(x_i < x_{mid}\) 的點和 \(x_i > x_{mid}\) 的點。兩兩列舉的時間複雜度顯然是不行的。

考慮最佳化。最佳化的核心是減少列舉次數,即根據一定條件過濾不必要的列舉。因此我們加入列舉條件:假設兩個區間內部的最優解是 \(d\) 只列舉距離分割線小於 \(d\) 的部分即可。但這樣的時間複雜度依然不夠優,因此需要考慮進一步最佳化。再加入列舉條件: 只計算縱座標相差小於 \(d\) 的點。可以透過將符合條件的點按照縱座標排序實現。

如此便可解決此題~

題外話

因為覺得呼叫sqrt會變慢,我一開始在分治的遞迴裡只算了模沒算距離,結果一直兩個點TLE,調了半天發現把sqrt放到算模數的那裡就行了,可能是double的大數運算太慢了吧... ?

AC程式碼

#include <algorithm>
#include <iostream>
#include <iomanip>
#include <stack>
#include <cmath>
#define int long long
using namespace std;

const int MAXN = 2e5 + 3;
int n;

class Point {
public:
    double x, y;
} p[MAXN];

inline double getDis(Point a, Point b) {
    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

bool cmpX(Point a, Point b) {return a.x < b.x;}
bool cmpY(Point a, Point b) {return a.y > b.y;}

Point tmp[MAXN];
double solve(int l, int r) {
    if (r - l == 1) return getDis(p[l], p[r]);
    int mid = (l + r) >> 1;
    double ans = min(solve(l, mid), solve(mid, r));
    double midx = p[mid].x;
    int cnt = 0;
    for (int i = l; i <= r; i++) {
        if (abs(p[i].x - midx) <= ans) {
            tmp[++cnt] = p[i];
        }
    }
    sort(tmp + 1, tmp + 1 + cnt, cmpY);
    for (int i = 1; i < cnt; i++) {
        for (int j = i + 1; j <= cnt && tmp[i].y - tmp[j].y < ans; j++) {
            ans = min(ans, getDis(tmp[i], tmp[j]));
        }
    }
    return ans;
}

signed main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> p[i].x >> p[i].y;
    }
    sort(p + 1, p + 1 + n, cmpX);
    cout << fixed << setprecision(4) << solve(1, n);
    return 0;
}

相關文章