平面最近点对

主要介绍分治做法

我们按照分治标准“分割-处理-上传”三步来做

首先将所有点按x值排序,分治函数solve(l , r)返回下标属于区间[l , r]的所有点的内部最近点对

显然当l == r 时不合法,因此我们上传一个+∞

假设对于solve(l , r)已经递归处理完了solve(l , mid) 和 solve(mid + 1 , r) (mid = (l + r) >> 1),那接下来就要处理下面这种情况

对于点对(A , B),A在[l , mid],且B在[mid + 1 , r]内

这种情况并不需要全部枚举左区间与右区间,我们有以下更优的做法

设dis = min(solve(l , mid) , solve(mid + 1 , r))

那么区间[l , r]的实际最优解ans肯定满足ans ≤ dis

令mid_x为下标mid的点的x值

那显然A,B的x均属于[mid_x - dis , mid_x + dis]

我们把这些点找出来再一一枚举即可

小优化:在[l , r]的子问题求解完后,我们可以把[l , r]的点按y排序,这样在最后处理时,对于当前枚举的i和j,假设i属于左边,j属于右边,若满足节点j的y - 节点i的y ≥ ans 那j以后的点对于i而言均不用再考虑了

由于根据以下性质

每处理一个左边的点时,右边最多只会有 6 个点被考虑到

于是对于每个点,最多处理6次,时间复杂度为O(6n)

结合分治的logn,实际时间复杂度为O(n log n)

P1429 模板题

跟上文完全一样,这里贴上代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N = 200010;
const double INF = 100000010;
struct nod
{
    double x , y;
} a[N] ,tmp[N];
int n , T;
bool cmp(nod a , nod b)
{
    return a.x < b.x;
}
double get_dist(nod a , nod b)
{
    double dx = a.x - b.x , dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}
double solve(int l , int r)
{
    if(l == r) return INF;
    int mid = (l + r) >> 1;
    double mid_x = a[mid].x;
    double ans = min(solve(l , mid) , solve(mid + 1 , r));
    int i = l , j = mid + 1 , cnt = l - 1;
    while(i <= mid && j <= r)
    {
        if(a[i].y < a[j].y) tmp[++cnt] = a[i++];
        else tmp[++cnt] = a[j++];
    }
    while(i <= mid) tmp[++cnt] = a[i++];
    while(j <= r) tmp[++cnt] = a[j++];
    for(int i = l ; i <= r ; i++)
        a[i] = tmp[i];
    cnt = 0;
    for(int i = l ; i <= r ; i++)
        if(a[i].x >= mid_x - ans && a[i].x <= mid_x + ans)
            tmp[++cnt] = a[i];
    for(int i = 1 ; i <= cnt ; i++)
        for(int j = i - 1 ; j && tmp[i].y - tmp[j].y <= ans ; j--)
            ans = min(ans , get_dist(tmp[i] , tmp[j]));
    return ans;
}
int main()
{
    scanf("%d" , &n);
    for(int i = 1 ; i <= n ; i++)
        scanf("%lf%lf" , &a[i].x , &a[i].y);
    sort(a + 1 , a + n + 1 , cmp);
    printf("%.4lf\n" , solve(1 , n));
    return 0;
}

附上上面性质的证明连接:点这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值