G55 平面最近点对 分治算法
视频链接:https://www.bilibili.com/video/BV1ko4y1s7Ed/
题意:给定平面上 n 个点,求最近点对的距离
思路:
- 对所有点按 x 为第一关键字,y 为第二关键字进行排序
- 分治求出中线两边的最小距离d1和d2,则 d=min(d1,d2)
- 划出中线左右距离为 d 的平行区域,搜集区域内的点;按纵坐标排序;检查纵坐标之差小于 d 的情况,更新 d 的值
时间:O(nlogn*logn)
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; const int N = 200010; struct Point{double x,y;} a[N],b[N]; bool cmp(Point &a,Point &b){ return a.x<b.x||(a.x==b.x&&a.y<b.y); } bool cmpy(Point &a,Point &b){ return a.y<b.y; } double dis(Point &a,Point &b){ return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } double devide(int l,int r){ if(l==r) return 2e9; //单点返回∞ if(l+1==r) return dis(a[l],a[r]); //邻点返回dis int mid=(l+r)>>1; double d=min(devide(l,mid),devide(mid+1,r));//中线两边取最小 int k=0; //跨中线处理 for(int i=l;i<=r;i++) //到中线的水平距离<d if(fabs(a[i].x-a[mid].x)<d) b[++k]=a[i]; sort(b+1,b+k+1,cmpy); //按y排序 for(int i=1;i<k;i++) //两点竖直距离<d for(int j=i+1;j<=k&&b[j].y-b[i].y<d;j++) d=min(d,dis(b[i],b[j])); return d; } int main(){ int n; cin>>n; for(int i=1;i<=n;i++)scanf("%lf%lf",&a[i].x,&a[i].y); sort(a+1,a+n+1,cmp); //按(x,y)排序 printf("%.4lf\n",devide(1,n)); }
// 分治+归并排序 // O(nlogn) #include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; const int N=200010; struct Point{double x,y;} a[N],b[N]; bool cmp(Point &a,Point &b){ return a.x<b.x||(a.x==b.x&&a.y<b.y); } double dis(Point &a,Point &b){ return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } double divide(int l,int r){ double d=2e9; if(l==r) return d; int m=(l+r)>>1; Point tmp=a[m]; d=min(divide(l,m),divide(m+1,r)); int i=l,j=m+1,k=0,t=0; while(i<=m && j<=r) if(a[i].y<a[j].y) b[k++]=a[i++]; else b[k++]=a[j++]; while(i<=m) b[k++]=a[i++]; while(j<=r) b[k++]=a[j++]; for(i=l,j=0; i<=r;) a[i++]=b[j++]; for(i=0;i<k;i++) if(fabs(tmp.x-b[i].x)<d) b[t++]=b[i]; for(i=0;i<t;i++) for(j=i+1;j<t&&b[j].y-b[i].y<d;j++) d=min(d,dis(b[j],b[i])); return d; } int main(){ int n; cin>>n; for(int i=1;i<=n;i++)scanf("%lf%lf",&a[i].x,&a[i].y); sort(a+1,a+n+1,cmp); //按(x,y)排序 printf("%.4lf\n",divide(1,n)); }
// 多重有序集 // O(nlogn) #include <algorithm> #include <cmath> #include <cstdio> #include <set> using namespace std; const int N=200005; int n; double ans=1e20; struct Point{double x, y;}; struct cmp{ bool operator()(const Point &a, const Point &b)const{ return a.x<b.x || (a.x==b.x&&a.y<b.y); } }; struct cmp_y{ bool operator()(const Point &a, const Point &b)const{ return a.y<b.y; } }; void update(const Point &a, const Point &b){ ans=min(ans,sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y))); } Point a[N]; multiset<Point, cmp_y> s; //多重有序集 int main(){ scanf("%d", &n); for(int i=0; i<n; i++) scanf("%lf%lf", &a[i].x, &a[i].y); sort(a, a+n, cmp()); for(int i=0, l=0; i<n; i++){ while(l<i && a[i].x-a[l].x>=ans) s.erase(s.find(a[l++])); for(auto it=s.lower_bound({a[i].x, a[i].y-ans}); it!=s.end() && it->y-a[i].y<ans; it++) update(*it, a[i]); s.insert(a[i]); } printf("%.4lf", ans); return 0; }