洛谷 P1429 平面最近点对(加强版)(分治,归并排序)
传送门
解题思路
按照x的大小分治。
然后归并排序。
归并时按照y值。
然后步入难点:
假设我们已经求出了左半部分的最近距离和右半部分的最近距离,两个距离的较小值设为d。
然后我们把划分左右两部分的中线的x值定为midx。
很显然,最终的答案有三种情况:
- 两点在左半部分
- 两点在右半部分
- 一个点在左边,一个点在右边(即跨过中线)
我们已经求出了1、2两种情况,还剩第三种情况。
对于第三种情况,只有距离中线的距离<=x的点才有可能成为最终答案。
所以我们在归并后,枚举一遍,找出所有abs(x-midx)<=d的点,放入数组q。
然后枚举一遍q数组,想一想另一个点可能在什么地方?答案只有两部分:
- 这个点上方的点
- 这个点下放的点
而且这两个点的y值的差一定小于d。
而我们刚刚的归并排序就是按照y值排序的,所以当某个点y值的差大于d时,可以直接结束循环。
对于时间复杂度,可以证明符合条件的点很少(最多6个)。
证明略。
注:求midx时一定要在分治前面求,否则分治结束后会改变点在数组中的位置(因为是按照y值归并的)。
AC代码
1 #include<iostream> 2 #include<algorithm> 3 #include<cmath> 4 #include<cstdio> 5 #include<iomanip> 6 using namespace std; 7 const int maxn=200005; 8 int n; 9 struct Point{ 10 double x,y; 11 }p[maxn],q[maxn]; 12 bool cmp(const Point &a,const Point &b){ 13 return a.x<b.x; 14 } 15 double dis(const Point &a,const Point &b){ 16 return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); 17 } 18 double divide(int l,int r){ 19 if(l==r) return 1ll<<60; 20 int mid=(l+r)/2; 21 double midx=p[mid].x; 22 double d=min(divide(l,mid),divide(mid+1,r)); 23 int p1=l,p2=mid+1,tot=0; 24 while(p1<=mid||p2<=r){ 25 if(p1<=mid&&(p2>r||p[p1].y<p[p2].y)){ 26 q[++tot]=p[p1++]; 27 }else{ 28 q[++tot]=p[p2++]; 29 } 30 } 31 for(int i=1;i<=tot;i++){ 32 p[l+i-1]=q[i]; 33 } 34 tot=0; 35 for(int i=l;i<=r;i++){ 36 if(abs(p[i].x-midx)<=d) q[++tot]=p[i]; 37 } 38 for(int i=1;i<=tot;i++){ 39 for(int j=i-1;j>=1&&q[i].y-q[j].y<=d;j--){ 40 d=min(d,dis(q[i],q[j])); 41 } 42 for(int j=i+1;j<=tot&&q[j].y-q[i].y<=d;j++){ 43 d=min(d,dis(q[i],q[j])); 44 } 45 } 46 return d; 47 } 48 int main() 49 { 50 cin>>n; 51 for(int i=1;i<=n;i++){ 52 scanf("%lf%lf",&p[i].x,&p[i].y); 53 } 54 sort(p+1,p+n+1,cmp); 55 cout<<fixed<<setprecision(4)<<divide(1,n)<<endl; 56 return 0; 57 }