[题解]P7883 平面最近点对(加强加强版)——分治解法
解题思路
我们用分治的思路来做。
首先将节点按\(x\)坐标从小到大排序,取中间的节点,把点集划分为\(2\)部分。
接下来我们假设左右\(2\)个部分已经求出答案了。
那么目前的答案就是左右答案的最大值。接下来我们只需要处理两区间相交的部分就可以了。
我们已经知道左右两边的最小值了,设它为\(d\)。那么我们从划分线开始向左右各取一个长度为\(d\)的区间,如下图:
我们要在紫色区域内选\(2\)个点,\(1\)个在划分线左边,\(1\)个在划分线右边。显然只有这样选才可能让答案更优。
我们对于紫色区域内的节点,按\(y\)坐标从小到大排序。
此时我们可以选择暴力枚举每一个节点(因为排过序所以\(j\)一定在\(i\)上面):
for(int i=1;i<cnt;i++){
for(int j=i+1;j<=cnt;j++){
int dis=dist(a[B[i]],a[B[j]]);
if(dis<d) d=dis;
}
}
但显然我们可以进行优化,与\(x\)坐标类似地,我们可以限制\(y_j-y_i<d\),毕竟\(y\)坐标相差超过\(d\)也一定不存在更小答案了嘛。
for(int i=1;i<cnt;i++){
for(int j=i+1;j<=cnt&&a[j].y-a[i].y<d;j++){
int dis=dist(a[B[i]],a[B[j]]);
if(dis<d) d=dis;
}
}
就酱。
总结一下全过程:
- 我们在主函数中先进行第\(1\)次排序,按\(x\)升序。这是为了能正常按\(x\)坐标分治。
- 接着在分治的过程中先进行归并排序,按\(y\)升序。
这一步的原因是我们本来就要对紫色区域进行排序(否则暴力遍历会超时),所以先把该区间内所有节点排序,再筛选出紫色区域内的节点即可。如果不用归并,先筛选出紫色部分的节点的话,相当于分治里套排序,时间复杂度应该就有\(2\)个\(\log\)了,而这种做法只有\(1\)个\(\log\)。 - 然后从按\(y\)排序后的数组中取出紫色部分的节点存起来,然后对于每一个节点,遍历它上面距离\(<d\)的节点,更新答案\(d\)即可。
坑点:
- 分割线(就是那个黄线)需要提前存下来,否则归并排序后位置会变。
- 这道题让输出答案的平方,所以如果你的\(d\)像我一样表示的是平方后的答案。那么注意枚举各种边界都需要用\(\sqrt{d}\),否则否则否则就会像我一样TLE 20pts调了半天调不出来!!!
Code
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 400010
using namespace std;
struct point{
int x,y;
}a[N],tmp[N];
int d=LLONG_MAX,n;
double sqrtd=1e15;
int squadist(point a,point b){
return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
void merge(int l,int r){
if(l==r) return;
int mid=(l+r)>>1,pos=1,p1=l,p2=mid+1,midx=a[mid].x;
merge(l,mid);
merge(mid+1,r);
while(p1<=mid&&p2<=r){
if(a[p1].y<a[p2].y) tmp[pos++]=a[p1++];
else tmp[pos++]=a[p2++];
}
while(p1<=mid) tmp[pos++]=a[p1++];
while(p2<=r) tmp[pos++]=a[p2++];
for(int i=l;i<=r;i++) a[i]=tmp[i-l+1];
pos=0;
for(int i=l;i<=r;i++)
if(fabs(a[i].x-midx)<sqrtd) tmp[++pos]=a[i];
for(int i=1;i<pos;i++){
for(int j=i+1;j<=pos&&tmp[j].y-tmp[i].y<sqrtd;j++){
int dis=squadist(tmp[i],tmp[j]);
if(dis<d) d=dis,sqrtd=sqrt(d);
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i].x>>a[i].y;
sort(a+1,a+1+n,[](point a,point b){return a.x<b.x;});
merge(1,n);
cout<<d<<"\n";
return 0;
}