题目描述:戳这里
题解:
这题是平面最近点对的裸题。
感觉平面最近点对还是比较玄妙的。
这个算法用到的是分治的思想,先分为左右两边 ,然后合并其。
那么就具体来讲讲吧。
首先我们先按照x坐标来排序n个点。
然后就是求解过程,根据我前面说的,对于一段区间[L…R],我们求出它的中点mid,然后分治[l…mid],[mid+1…R]。
我们假设d=两段中较小的最短长度。
那么我们当前还要考虑合并的情况,也就是左边取一个点,右边取一个点的最短长度,我们要用它来修正d。
那么有一种比较暴力的方法,直接暴力枚举两边的元素,这样做的复杂度甚至比
O(n2)
O
(
n
2
)
还要大。
但是我们可以在这种暴力的方法上进行一些优化。
我们可以考虑一下,那些点可能能够修正d,肯定至少要是x坐标距离中点mid的x坐标的距离
<d
<
d
<script type="math/tex" id="MathJax-Element-421">
我们可以从L~R扫一遍,找出这些点。
但是这样的总点数还是很多,如果有一大堆点都在mid附近的话复杂度还是会爆炸。
既然x坐标我们已经考虑了,我们干脆也限制一下y坐标。
我们将找出的这些点按y坐标排序,然后枚举每一个点i,用j向后推,如果j的y坐标-i的y坐标>0就可以break。
那么其实可以证明,这样的j最多只会有不超过8个,所以复杂度比较科学。
总复杂度:分制log,排序log,总共
O(n∗log2(n))
O
(
n
∗
l
o
g
2
(
n
)
)
。
当然也可优化为
O(n∗log(n))
O
(
n
∗
l
o
g
(
n
)
)
,这里就不讲了。
代码如下:
#include<cstdio>
#include<string>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=100005;
const double inf=1e9;
int T,n,b[maxn];
struct dyt{
double x,y;
}a[maxn];
double cmp(dyt a,dyt b) {return a.x<b.x;}
double cmp1(int x,int y){return a[x].y<a[y].y;}
double getdist(int i,int j){
return sqrt(1.0*(a[i].x-a[j].x)*(a[i].x-a[j].x)+1.0*(a[i].y-a[j].y)*(a[i].y-a[j].y));
}
int abs_(int x){if (x<0) return -x; return x;}
double find(int l,int r) {
if (l>=r) return inf;
if (l+1==r) return getdist(l,r);
int mid=(l+r)>>1;
double d=min(find(l,mid),find(mid+1,r));
int m=0;
for (int i=l;i<=r;i++)
if (abs_(a[i].x-a[mid].x)<=d) b[++m]=i;
sort(b+1,b+1+m,cmp1);
for (int i=1;i<=m;i++)
for (int j=i+1;a[b[j]].y-a[b[i]].y<d&&j<=m;j++)
d=min(d,getdist(b[i],b[j]));
return d;
}
int main(){
while (~scanf("%d",&n)) {
if (n==0) break;
for (int i=1;i<=n;i++) {
scanf("%lf %lf",&a[i].x,&a[i].y);
}
sort(a+1,a+1+n,cmp);
printf("%.2lf\n",find(1,n)/2.0);
}
return 0;
}