UVA12001 UVa Panel Discussion 题解

原题链接: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;
}
posted @ 2020-05-11 23:47  with_hope  阅读(155)  评论(0编辑  收藏  举报