HDU1007 Quoit Design(分治)

题目大意:

        在平面上有很多玩具,要用一个环去套玩具,而且一个环一次只能套中一个玩具,求出环的最大半径。因为一个环一次只能套中一个玩具,所以这个环的最大半径就是相距最近的两个玩具的距离的一半,超过这个最大半径,就有可能一次将相距最近的两个玩具同时套住。所以问题就简化为:给出一个平面内的所有点的坐标,求出距离最近的两个点之间的距离。

解题思路:

        数据范围是100000,最初我用暴力解法,果不其然TLE了,然后就去网上看题解。大神用了分治的思路,将平面不断递归地切成左右两半,分别求左右两半平面的最小距离,使问题最终被细化为求相邻两个点之间的距离,然后将结果回溯。在回溯的过程中,特殊处理一下两个平面分界线附近的点,有可能一个在左平面、一个在右平面的点之间的距离更小但我们没有计算到。而这种情况就是本题的难点。

算法解析:

        将所有坐标按X坐标排序,取最中间的点,将一个平面地切分为左右两块S_{L}S_{R},然后分别对左右平面再进行同样的操作。两个平面内的点的最小距离分别为:\delta _{L}\delta _{R},取他们两者的最小值\delta = min(\delta _{L}\delta _{R}),\delta就是在没有考虑分界线“附近”的点的情况下的结果;接下来,我们就要考虑在这两个平面的分界线附近有没有距离小于\delta的两个点。首先明确一点,如果这对点存在,那么一定其中一个点在S_{L}上,另一个在S_{R}上。否则,如果两个点在同一个平面上的话就与刚才求的\delta _{L}\delta _{R}矛盾了。我们在分界线附近划出两个宽度为\delta的区域。如图:

(图片来自:https://blog.csdn.net/Sun1956/article/details/8294048

        我们首先要找到在这个区域内的所有点。因为我们是将平面按X坐标左右分开的,那么就只需判断平面中其他点和分界线处的点的x坐标的差,如果这个差小于\delta,那么这个点就在这个区域内。找到点之后,有可能这些点的数量还是很大,我们还要用两个方法尽量简化计算。

        1、将这些点按y坐标排序,然后二重循环判断每对点的距离。但若是两个点的y坐标之差大于了\delta,则内层循环就可以直接终止了,因为当前点是按y坐标排序的,再后面的点与当前点的距离肯定更大了,无需考虑。

        2、对于一个点只需要判断y坐标比它大的6个点,就可以找到满足条件的点了。我们假设当前遍历到p点,沿p点画一条平行于X轴的横线,以这条横线与分界线为边,作两个紧挨着的正方形,这6个点就是这两个正方形的6个顶点。如下图:

(图片来自:https://blog.csdn.net/Sun1956/article/details/8294048

        首先我们再次明确刚才说过的一点:\delta是这个平面内的最小差值。举个反例:假设有7个点与p点距离小于\delta,其中有个点是q。那么这个q点一定在右平面的两个正方形内,那么q点与正方形的6个顶点的中的某个顶点的距离就小于\delta了,前后矛盾。所以我们只需要判断6个点。超过6个点的话,再后面其他点对的距离一定大于\delta,而这些点对于我们没有用。求出这些点对的距离,然后与\delta做比较,保留最小值。

c++代码:

#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
struct point
{
	double x,y;
}a[100001];
//w数组用来保存在分界线附近的点的编号 
int w[100001];
//将所有点按x坐标排序 
bool cmpx(const point &a,const point &b)
{
	return a.x<b.x;
}
//将分界线附近的点按y坐标排序,不需要改动坐标数组,只修改w数组即可 
bool cmpy(const int &m,const int &n)
{
	return a[m].y<a[n].y;
}
//求两个点之间的距离 
double mix(int m,int n)
{
	return sqrt((a[m].x-a[n].x)*(a[m].x-a[n].x)+(a[m].y-a[n].y)*(a[m].y-a[n].y));
}
double res(int left,int right)
{
	if(left==right)
		return 10000000;
	if(left+1==right)
		return mix(left,right);
	int mid=(left+right)/2;
	//切分平面 
	double d1=res(left,mid);
	double d2=res(mid+1,right);
	double d=min(d1,d2);
	int k=0;
	//找出左右平面中在分界线“附近”的点 
	for(int i=left;i<=right;i++)
	{
		//只保留点的坐标即可 
		if(fabs(a[i].x-a[mid].x)<d)
			w[k++]=i;
	}
	sort(w,w+k,cmpy);
	for(int i=0;i<k-1;i++)
	{
		//只需要向后搜索6个点,再后面的可以舍掉 
		for(int j=i+1;j<k && j<i+7;j++)
		{
			if(a[w[j]].y-a[w[i]].y>=d)
				break;
			d=min(d,mix(w[j],w[i]));
		}
	}
	return d;
}
int main()
{
	int i,n;
	while(scanf("%d",&n)!=0)
	{
		if(n==0)
			break;
		for(int i=0;i<n;i++)
			scanf("%lf%lf",&a[i].x,&a[i].y);
		sort(a,a+n,cmpx);
		printf("%.2f\n",res(0,n-1) / 2);
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值