原题链接:UVA12001 UVa Panel Discussion
题目大意:给定平面上的(n)个点,询问两两之间距离的最小值((n le 10000)且当答案(> 10000)时输出( ext{INFINITY}))。
题解:看到这题我的第一反应是建 k-D Tree 然后再暴力找到距离每个点最近的点,时间复杂度应该是期望(O(nsqrt{n})),然后发现(O(n^2))只差一点,暴力改改其实就能过了,但是,这一题的数据范围貌似是可以放到(10^5),那么这个时候就需要找到一个更加高效的算法。
考虑对平面上的点分治,每次按照 x 轴切成两半,左右递归处理,接下来只需要处理跨过中间线的点对就可以了,然而暴力处理并不能让这一道题的时间复杂度更优,考虑利用左右两侧的答案进行剪枝。
假设当前的答案为(ans),那么显然离中间线的距离超过(ans)的点可以不用处理了,对于一个点((x_i,y_i)),我们只需要处理(y)的范围在(y_i-ans<y<=y_i)的点就可以了(同 x 轴的方法一样)。
接下来就是一个很像 CDQ 分治的东西,这样,递归的深度为(O(log n)),每一层有期望(O(n))个操作,所以总的时间复杂度是期望(O(nlog n))。
下面放代码:
#include <cmath>
#include <vector>
#include <cstdio>
#include <algorithm>
using namespace std;
const int Maxn=10000;
struct Node{
double x,y;
friend bool operator <(Node p,Node q){
if(p.y==q.y){
return p.x<q.x;
}
return p.y<q.y;
}
}a[Maxn+5];
bool cmp(Node p,Node q){
if(p.x==q.x){
return p.y<q.y;
}
return p.x<q.x;
}
int n;
double ans;
void calc(int left,int right){
if(left==right){
return;
}
int mid=(left+right)>>1;
double line=(a[mid].x+a[mid+1].x)/2;
calc(left,mid);
calc(mid+1,right);
inplace_merge(a+left,a+1+mid,a+1+right);
vector<Node> b;
for(int i=left;i<=right;i++){
if(fabs(a[i].x-line)>=ans){
continue;
}
for(int j=(int)b.size()-1;j>=0;j--){
double dx=a[i].x-b[j].x;
double dy=a[i].y-b[j].y;
if(dy>=ans){
break;
}
ans=min(ans,sqrt(dx*dx+dy*dy));
}
b.push_back(a[i]);
}
}
int main(){
while(~scanf("%d",&n)){
if(n==0){
break;
}
for(int i=1;i<=n;i++){
scanf("%lf%lf",&a[i].x,&a[i].y);
}
sort(a+1,a+1+n,cmp);
ans=10001;
calc(1,n);
if(ans>10000.0){
puts("INFINITY");
}
else{
printf("%.4lf
",ans);
}
}
return 0;
}