正题
题目链接:https://www.luogu.com.cn/problem/P1429
题目大意
平面上nnn个点,求最近点对
解题思路
考虑分治求最近点对,首先将平行于yyy轴将平面穿过xxx左边的中位数分割成两半,现在最近点对有三种可能,
- 在分割线左边
- 在分割线右边
- 穿过分割线
我们知道1和2可以用分治到两边计算,考虑如何求情况3。暴力枚举对数肯定会TLETLETLE,考虑优化,假设我们已经知道1和2的最小解d1,d2d1,d2d1,d2了,我们有d=min{d1,d2}d=min\{d1,d2\}d=min{d1,d2},那么我们可以以分割线为对称轴做一个2d∗d2d*d2d∗d的矩形,最近点对的两个点一定在这个矩形内。
这样计算为什么可以优化算法?因为矩阵内的点数是常数级别的,首先我们可以由分割线分割成两个小正方形,我们知道正方形内的点距离一定不小于ddd,那么一个正方形内最多只有常数级别的点数(好像是3个的样子)
所以时间复杂度O(nlogn)O(n\log n)O(nlogn)
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=2e5+10;
int n,p[N],c[N];
double x[N],y[N];
bool cmp(int a,int b)
{return (x[a]==x[b])?(y[a]<y[b]):(x[a]<x[b]);}
bool cMp(int a,int b)
{return y[a]<y[b];}
double get_dis(int a,int b)
{return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));}
double Solve(int l,int r){double d=1e18;if(l==r)return d;if(l+1==r)return get_dis(p[l],p[r]);int mid=(l+r)>>1;double d1=Solve(l,mid);double d2=Solve(mid+1,r);d=min(d1,d2);int tot=0;for(int i=l;i<=r;i++)if(fabs(x[p[i]]-x[p[mid]])<=d)c[++tot]=p[i];sort(c+1,c+1+tot,cMp);for(int i=0;i<tot;i++){for(int j=i+1;j<=tot;j++){if(y[c[j]]-y[c[i]]>=d)break;double D=get_dis(c[i],c[j]);d=min(d,D);}}return d;
}
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%lf%lf",&x[i],&y[i]);p[i]=i;}sort(p+1,p+1+n,cmp);printf("%.4lf",Solve(1,n));
}