题目链接
题目大意是,给你n个点,要求出这n个点中,距离最近的两个点的距离;
这题正解应该是分治;
先上一篇大牛的blog
计算几何 平面最近点对 nlogn分治算法 求平面中距离最近的两点
然后说一下我自己的理解和思路;
首先是分治的思想,对于n个点,我们可以把这些点按照x轴排序后,再分成两半,每一半中点的数量都不超过n/2,可能还有一部分点处于分割线上;
然后我们只要求出左边和右边n/2个点钟的最近点对的距离,记为
和
;
其中的较小值为
=min{
,
};
但是此时还有一种情况,就是有两个点,一个处于左边,一个处于右边(或者处于分割线上);
现在考虑两个点各在一边的情况:
首先,我们可以肯定:
这两个点的范围一定包含在
;
也就是说,这个范围以外的点,都不需要考虑,然后我们可以把需要考虑的点加入到一个数组中;
for(left to right){
if(a[i]∈[mid−σ,mid+σ])
把a[i]加入到数组中存储
}
看下图,然后我们想到,比如对于左边的一个点p,右边和他组成最小距离的点,一定处于右边这个
的矩形框内;
我们可以怎样利用这个性质呢;
可以先把
内的点按照y坐标排序;
然后在枚举比较距离的时候,我们可以进行剪枝,在y坐标间隔超出
时,直接break(因为后面的点间隔更大);
这样实现:
double solve(int left,int right){
if(left+1==right) return dis(a[left],a[right]);
double xl,xr,xm;
int mid=(left+right)>>1;
xl=solve(left,mid);
xr=solve(mid,right);
xm=min(xl,xr);
int k=1;
for(int i=left;i<=right;i++){
if(fabs(a[mid].x-a[i].x)<=xm) b[k++]=a[i];
}
sort(b+1,b+k,cmp);
for(int i=1;i<k;i++){
for(int j=i+1;j<k && (b[j].y-b[i].y<xm);j++){
xm=min(xm,dis(b[i],b[j]));
}
}
return xm;
}
然后我们想到,其实我们按照y坐标完成排序之后,从下往上遍历左边点的时候,我们发现,其实右边的那个可能答案的矩形,是单调向上移动的, 所以也可能使用一个two-pointer来做这个扫描,具体的代码没有尝试了;
最终ac代码如下:
#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxx=1e5+6;
class point {
public:
double x,y;
point(double x=0,double y=0):x(x),y(y){}
bool operator < (const point &u)const{
if(x!=u.x) return x<u.x;
return y<u.y;
}
};
point a[maxx],b[maxx];
inline double dis(point a,point b){ return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); }
inline bool cmp(point a,point b){return a.y<b.y;}
double solve(int left,int right){
if(left+1==right) return dis(a[left],a[right]);
double xl,xr,xm;
int mid=(left+right)>>1;
xl=solve(left,mid);
xr=solve(mid,right);
xm=min(xl,xr);
int k=1;
for(int i=left;i<=right;i++){
if(fabs(a[mid].x-a[i].x)<=xm) b[k++]=a[i];
}
sort(b+1,b+k,cmp);
for(int i=1;i<k;i++){
for(int j=i+1;j<k && (b[j].y-b[i].y<xm);j++){
xm=min(xm,dis(b[i],b[j]));
}
}
return xm;
}
int main() {
int n;
double x,y;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lf%lf",&x,&y);
a[i]=point(x,y);
}
sort(a+1,a+1+n);
double ans=solve(1,n);
printf("%.3f\n",ans);
return 0;
}
错点:
1.后面操作的是b数组,一开始写错了,写成a数组了;
2.在开启std::ios::sync_with_stdio(false);后,不能流读写和标准读写混合使用,否则oj读写有问题;
然后,因为这题的数据比较简单,也可以使用暴力+剪枝卡过,代码如下:
只要在前面的x已经大于ans的情况下,就可以不用遍历后面的的x了;剪枝效果很棒;
#include <cmath>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxx=1e5+6;
struct point{
double x,y;
};
point a[maxx];
inline bool cmp(point a,point b){
if(a.x!=b.x) return a.x<b.x;
else return a.y<b.y;
}
inline double f(point a,point b){
return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
inline double g(double x){return x*x;}
int main() {
std::ios::sync_with_stdio(false);
int n,x,y;
cin>>n;
for(int i=1;i<=n;i++){
cin>>x>>y;
a[i].x=x;a[i].y=y;
}
sort(a+1,a+1+n,cmp);
double mi=0x7fffffff;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(g(a[i].x-a[j].x)>=mi) break;
mi=min(mi,f(a[i],a[j]));
}
}
mi=sqrt(mi);
printf("%.3f\n",mi);
return 0;
}
然后,大神说还有一种乱搞的办法解这道题;
先对所有点按照x坐标排序之后,对于每一个点,不去遍历其他的所有点,而是只遍历它x轴附近的一些点,随便设定一个阈值,比如100个点,然后可能可以过;
但是这样的搞法容易被数据卡,所以还能优化;
想到,这样的解法如果有问题,肯定是有这样的点,比如点a和点b的x轴相距很近,但是y轴相聚很远,但是在x轴相聚比较远的地方,可能存在一个点c,与点a的y轴距离很近,如果这样的话,就会wa;
但是道高一尺魔高一丈,我们可以把整个平面旋转一个任意的角度,然后再按照上面的方法来一次,这样我们基本就可以确定答案是正确的了;(逃