一.平面最近点对问题.
平面最近点对:在一个平面上有 n n n个点,求出距离最近的两个点.
平面最近点对是计算几何中一个十分经典且基础的问题,通常采用分治法来解决.
二.直线最近点对的分治法.
在用分治法解决平面最近点对之前,我们用分治法来解决一下直线最近点对问题.
考虑在一条直线上暴力枚举两个点并计算距离取最小,很明先复杂度是 O ( n 2 ) O(n^2) O(n2)的,比较慢.
于是我们考虑先给 n n n的个点按照坐标排序,然后分治,每次分成左右两段;然后合并回去,每次大区间的答案为两个小区间的答案和左区间最右边的点与右区间最左边的点的距离的最小值.
容易发现这个算法的时间复杂度为 O ( n ) O(n) O(n),但由于排序复杂度为 O ( n log n ) O(n\log n) O(nlogn).
当然这个问题有一个更简单的做法——直接大力排序后将第
i
i
i个点和第
i
+
1
i+1
i+1个点的距离取最小值.
三.平面最近点对的分治法.
我们考虑一下如何把一维的分治法拓展到二维上.
考虑仍然按照横坐标 x x x排序,然后每次通过中位数分成两组,递归后答案合并上来.
合并的过程可以通过两个小区间的答案与大力枚举一个点在左区间另一个点在右区间的距离取最小值…等等大力枚举不是 O ( n 2 ) O(n^2) O(n2)的么…
这怎么办呢?能不能优化一下呢?
考虑每一次先求出左区间和右区间的答案的最小值 d d d,很明显跨界的两个点的横坐标与分治中心不应该超过 d d d.
那么先把所有横坐标与分治中心不超过 d d d的所有点提取出来,接下来我们只考虑这些点.
考虑把这些点按照纵坐标 y y y排序,然后大力枚举每一个点并再枚举这个点后面的点取最小值.容易发现后面的点与这个点的纵坐标之差大于 d d d就肯定可以退出了,直接break掉.
这样子做貌似还是
O
(
n
2
)
O(n^2)
O(n2)才能合并,但事实上这样子是
O
(
n
log
2
n
)
O(n\log^2n)
O(nlog2n)的算法,下面我们将会分析时间复杂度.
四.分治法的时间复杂度.
很容易发现这个分治法复杂度的关键在于合并过程,而合并过程的复杂度瓶颈在于一个点最多可以有多少个候选点.
我们观察一个蓝色点的候选点,发现蓝点的候选点必然在它下面的由两个边长为 d d d的正方形组成的长方形中.
考虑对于这个长方形,它最多会被当前我们选的中点所在的平行于 y y y轴的直线分成两半,由于每一半中任意两个点的距离至少为 d d d,所以长方形中所有的点的数量是一个常数,经过计算长方形中所有点的数量上限为 6 6 6.
那么很明显这个合并算法的复杂度就只有
O
(
6
n
)
O(6n)
O(6n)了,那么总复杂度就是
O
(
n
log
2
n
)
O(n\log^2n)
O(nlog2n)的.
五.例题与代码.
例题:luogu1429.
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=200000;
const double INF=1e200;
int n;
struct point{
double x,y;
}d[N+9],t[N+9];
bool cmp1(const point &a,const point &b){return a.x<b.x;}
bool cmp2(const point &a,const point &b){return a.y<b.y;}
double sqr(double x){return x*x;}
double Get_dis(point a,point b){return sqrt(sqr(a.x-b.x)+sqr(a.y-b.y));}
double Divide(point *d,int L,int R){
if (L==R) return INF;
int mid=L+R>>1,tt=0;
double res=min(Divide(d,L,mid),Divide(d,mid+1,R));
for (int i=L;i<=R;++i)
if (abs(d[i].x-d[mid].x)<=res) t[++tt]=d[i];
sort(t+1,t+1+tt,cmp2);
for (int i=1;i<=tt;++i)
for (int j=i+1;j<=tt&&t[j].y-t[i].y<res;++j)
res=min(res,Get_dis(t[i],t[j]));
return res;
}
double Solve(point *d,int n){
sort(d+1,d+1+n,cmp1);
return Divide(d,1,n);
}
Abigail into(){
scanf("%d",&n);
for (int i=1;i<=n;++i)
scanf("%lf%lf",&d[i].x,&d[i].y);
}
Abigail outo(){
printf("%.4lf\n",Solve(d,n));
}
int main(){
into();
outo();
return 0;
}