http://acm.hdu.edu.cn/showproblem.php?pid=1007
题意就是给你n个点,输出最近两点距离的一半。
这道题用到了分治的思想。就是将一个大问题划分规模较小的子问题,然后对子问题进一步划分求解,最后将子问题的解进行合并。
这道题目在划分问题上显得并不困难,可以沿着坐标轴进行划分,将点的划分成数量尽量相等的两份,分别求出左右区域的最短距离。但是在进行合并的时候却没有那么容易,如果枚举所有的点,那么效率就太低了。
在求解跨越分界线的点对时,首先可以排除的是与分界线距离超过左右区域内最短距离的点,如果存在大于此距离的点,那么必然不可能成为最近点对。
其次,在对每个点进行遍历时,如果两点的纵坐标大于左右区域内最短距离,那么就不必再搜索纵坐标差值更大的点。
所以基本的流程就是这样:
首先对所有点按X坐标进行排序,找出中点进行划分,左右分别进行求解。
然后找到对左右两侧的最短距离min,舍弃距离中点超过min的点,对剩余点按照Y坐标排序,对每个点进行遍历,如果两点Y轴距离大于min,那么就不再搜索距离更大的点,直接进行下一个点的搜索。
我还加了个小剪枝,就是分治过程中出现min=0,那么就直接返回。好像有那么一点点用,提高了20ms。。。
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
using namespace std;
struct node
{
double x,y;
}p[100005];
int c[100005],flag;
double min(double a,double b)
{
return a<b?a:b;
}
bool cmp(node a,node b)
{
return a.x<b.x;
}
bool cmp2(int a,int b)
{
return p[a].y<p[b].y;
}
double dis(int a,int b)
{
return
sqrt((p[a].x-p[b].x)*(p[a].x-p[b].x)+(p[a].y-p[b].y)*(p[a].y-p[b].y));
}
double merge(int low,int up)
{
int i,j,mid,cnt=0;
if(flag==1) return 0;
if(up-low==2)
return dis(low,up-1);
if(up-low==3)
return min(dis(low,up-1),dis(low,up-2)),dis(up-1,up-2);
mid=(up+low)/2;
double ans=min(merge(low,mid),merge(mid,up));
if(ans==0) flag==1;
for(i=low;i<up;i++)
if(p[i].x>p[mid].x-ans&&p[i].x<p[mid].x+ans)
c[cnt++]=i;
sort(c,c+cnt,cmp2);
for(i=0;i<cnt;i++)
{
for(j=i+1;j<cnt;j++)
{
if(p[c[j]].y-p[c[i]].y>=ans)
break;
ans=min(ans,dis(c[i],c[j]));
}
}
return ans;
}
int main()
{
int n,i;
while(scanf("%d",&n)&&n)
{
flag=0;
for(i=0;i<n;i++)
{
scanf("%lf%lf",&p[i].x,&p[i].y);
}
sort(p,p+n,cmp);
printf("%.2lf\n",merge(0,n)/2);
}
return 0;
}