题解 P7883 【平面最近点对(加强加强版)】
题目大意
给定平面上
n 个点,求出两点间欧几里得距离的最小值。
题解
由于本题是模板题,因此本题解尽量详细地讲述怎么使用分治法计算出平面最近点对的距离。一些奇技淫巧(比如随机旋转坐标系什么的),这里就不提了。
容易发现,坐标系上这些点的顺序并不会影响到最终的求解。因此可以先将所有的点按照
分治法的基本思路是,将大问题转化为类似的小问题,解决小问题后通过合并解决大问题。在本题中,我们要解决的问题就是计算标号为
容易发现,当
这里生成了
按照
注意,这里使用的是左闭右开区间。因此
P_8 应该在蓝色部分,而非红色部分。
对于
当红色部分被划分成了绿色部分和红色部分后,可以发现这两个子问题内都只有两个点了。因此直接更新最短距离
下面考虑如何将绿色部分和红色部分的结果进行合并。也就是计算左边的点和右边的点相连对答案进行的更新。
将距离紫红色线段不超过
处理完
接着是处理
此时红色部分被完全处理完了。
处理完红色部分,再处理完蓝色部分,最后将橙色线段两侧距离不超过
看上去这个做法在合并时是非常暴力的(因为一旦绿色部分内的点数非常多,就会导致复杂度退化)。但是可以证明,绿色部分内的点不超过
考虑这样的一张图。容易发现,不可能会有某个点落入圆内,否则在合并之前求得的
现在假设我们已经给
双指针对应了这样一种思想:
指针
考虑计算整个算法的时间复杂度。
设
瓶颈在于每次合并答案时进行的排序。同样是分治思想,可以使用归并排序,在左右子区间的节点都排好序后,
参考代码
#include<bits/stdc++.h>
#define up(l,r,i) for(int i=l,END##i=r;i<=END##i;++i)
#define dn(r,l,i) for(int i=r,END##i=l;i>=END##i;--i)
using namespace std;
typedef long long i64;
const int INF =2147483647;
struct Point{int x,y;};
typedef vector<Point>::iterator Iter;
bool cmpx(const Point a,const Point b){return a.x<b.x;}
bool cmpy(const Point a,const Point b){return a.y<b.y;}
double dis(const Point a,const Point b){
return sqrt(pow(a.x-b.x,2)+pow(a.y-b.y,2));
}
void slv(const Iter l,const Iter r,double &d){
if(r-l<=1) return;
vector<Point> Q; Iter t=l+(r-l)/2;double w=t->x;
slv(l,t,d),slv(t,r,d),inplace_merge(l,t,r,cmpy);
for(Iter x=l;x!=r;++x)
if(abs(w-x->x)<=d) Q.push_back(*x);
for(Iter x=Q.begin(),y=x;x!=Q.end();++x){
while(y!=Q.end()&&y->y<=x->y+d) ++y;
for(Iter z=x+1;z!=y;++z) d=min(d,dis(*x,*z));
}
}
vector<Point> X; int n; double ans=1e18;
int qrd(){
int w=1,c,ret;
while((c=getchar())> '9'||c< '0') w=(c=='-'?-1:1); ret=c-'0';
while((c=getchar())>='0'&&c<='9') ret=ret*10+c-'0';
return ret*w;
}
int main(){
n=qrd(); up(0,n-1,i){
int x=qrd(),y=qrd(); X.push_back({x,y});
}
sort(X.begin(),X.end(),cmpx),slv(X.begin(),X.end(),ans);
printf("%.0lf\n",ans*ans);
return 0;
}