【书上讲解】平面上最近点对问题
给你二维平面上的n个点,让你求出其中最近的点对。
【题解】
这是一个分治的问题。 可以这样做: 首先将n个点按照x升序排。 然后将l..r这个区间内的点分成 l..mid和mid+1..r两个部分递归求解。 分别求出这两段里面的点的最近点对的距离d1和d2 然后令d = min(d1,d2) 这是最后答案的点都在其中一边的情况。 现在考虑最后答案的点对分别在左区间p1和右区间p2的情况。 首先可以将左区间和右区间中点的横坐标和分割线距离超过d的点去掉。 然后对于每一个p1,在其右侧画一个长d,高2d的矩形。 就会发现那个矩形内最多只会有6个右区间中的点(鸽巢原理) 所以我们只需要将l..r这个区间内的点按照y轴升序排一下序就好。 然后对于每个点(x,y)检查y坐标的范围在(y-d,y+d)的其他点就好. 但这样排序的话,时间复杂度是n*logn*logn级别的。 我们可以在进行答案的合并的时候,顺便对每个子区间做一下归并排序(按照y轴). 然后归并排序的时候,对于左区间的点,顺便遍历和它的y坐标差的绝对值小于d的其他点即可 这样时间复杂就是n*logn级别的了【代码】
#include <cstdio>
#include <cmath>
using namespace std;
const int N = 1e5;
const int oo = 1e9;
struct abc{
int x,y;
};
int n;
abc a[N+10];
abc b[N+10];
int sqr(int x){return x*x;}
int dis(abc a,abc b){
return sqr(a.x-b.x)+sqr(a.y-b.y);
}
int min(int x,int y){
if (x<y) return x;else return y;
}
int divide(int l,int r){
if (l>=r) return oo;
int mid = (l+r)>>1;abc mp = a[mid];
int d = divide(l,mid);d = min(d,divide(mid+1,r));
int i = l,j = mid + 1,k = l;
while (i<=mid || j<=r){
while (j<=r && (i>mid || a[j].y<=a[i].y)) b[k++] = a[j++];
if (i<=mid && mp.x-a[i].x<d){//a[i]是分割线左边的点
//此时a[mid+1..j-1]全都小于等于a[i]的纵坐标
//a[j..r]全都大于a[i]的纵坐标。(且这两段的点都是分割线右边的点)
for (int l = j-1;l>=mid+1;l--){
if (sqr(a[i].y-a[l].y)>d) break;
int temp = dis(a[l],a[i]);
d = min(temp,d);
}
for (int l = j;l<=r;l++){
if (sqr(a[l].y-a[i].y)>d) break;
int temp = dis(a[l],a[i]);
d = min(temp,d);
}
}
if (i<=mid) b[k++] = a[i++];
}
for (int i = l;i<= r;i++) a[i] = b[i];
return d;
}
void _swap(abc *x,abc *y){
abc t = *x;
*x = *y;
*y = t;
}
void kp(abc a[],int l,int r){
abc temp = a[l];
int i = l,j = r+1;
while(true){
while (a[++i].x<temp.x && i<r);
while (a[--j].x>temp.x);
if (i>=j) break;
_swap(&a[i],&a[j]);
}
a[l] = a[j];
a[j] = temp;
if (l<j) kp(a,l,j-1);
if (j<r) kp(a,j+1,r);
}
int main(){
scanf("%d",&n);
for (int i = 1;i <= n;i++) scanf("%d%d",&a[i].x,&a[i].y);
kp(a,1,n);
double ans = sqrt(1.0*divide(1,n));
printf("%.3f\n",ans);
return 0;
}