原题链接:UVA12001 UVa Panel Discussion
题目大意:给定平面上的\(n\)个点,询问两两之间距离的最小值(\(n \le 10000\)且当答案\(> 10000\)时输出\(\text{INFINITY}\))。
题解:看到这题我的第一反应是建 k-D Tree 然后再暴力找到距离每个点最近的点,时间复杂度应该是期望\(O(n\sqrt{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(n\log 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\n",ans);
}
}
return 0;
}
UVA12001 UVa Panel Discussion 题解
原文:https://www.cnblogs.com/withhope/p/12873341.html