您的位置:首页 > 其它

杭电1007————最近点对问题(分治问题)

2014-08-07 19:19 501 查看

Quoit Design

Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)

Total Submission(s): 30677 Accepted Submission(s): 8060



Problem Description
Have you ever played quoit in a playground? Quoit is a game in which flat rings are pitched at some toys, with all the toys
encircled awarded.

In the field of Cyberground, the position of each toy is fixed, and the ring is carefully designed so it can only encircle one toy at a time. On the other hand, to make the game look more attractive, the ring is designed to have the largest radius. Given a
configuration of the field, you are supposed to find the radius of such a ring.

Assume that all the toys are points on a plane. A point is encircled by the ring if the distance between the point and the center of the ring is strictly less than the radius of the ring. If two toys are placed at the same point, the radius of the ring is considered
to be 0.

Input
The input consists of several test cases. For each case, the first line contains an integer N (2 <= N <= 100,000), the total
number of toys in the field. Then N lines follow, each contains a pair of (x, y) which are the coordinates of a toy. The input is terminated by N = 0.

Output
For each test case, print in one line the radius of the ring required by the Cyberground manager, accurate up to 2 decimal
places.

Sample Input

2
0 0
1 1
2
1 1
1 1
3
-1.5 0
0 0
0 1.5
0


Sample Output

0.71
0.00
0.75题目大意如下:就像是玩的套圈游戏,告诉你每一个玩具的坐标,你要求铁圈的最大半径。就是求出所有玩具中任意一对物品的距离最小值,该最小值除以2就是最大的铁圈半径了。这是分治的一个经典问题————最近点对问题。这个问题拿到手想的最简单的办法就是暴力枚举,很显然在10W的数据量下必然超时..给出一种二分法的思想。
参考资料 :http://www.cnblogs.com/hxsyl/p/3230164.html(很重要)http://blog.csdn.net/lonelycatcher/article/details/7973046(解释的过于精简,估计也是参考的上边的那个连接)

首先,我们先看一维的这样的问题...(最简单的就是按照x从小到大排序,然后遍历就可以了,这个很简单,但是为了推广到二维,我们用分治的思想)



如上图片,我们把所有点的点按照从小到大的顺序排好后,将所有点看成一个集合S以结点m(m是集合S元素的中位数)为中心,将集合S划分为个数大致相等的S1和S2。从S1中求出的最小点对距离为 : dis1 = |p1-p2|从S2中求出的最小点对距离为 : dis2 = |q1-q2|那么min(dis1,dis2)是不是就是整个集合的最小点对呢?答案是否定的。因为最小点对的两个点还有可能是一个来自S1(p3)一个来自S2(q3),假设就是 dis3 = |p3-q3|如果答案是dis3 那么dis3大小怎么判断(即p3,q3到底是多少?)?方法如下:因为dis3 = |p3 - q3| < dis又p3 q3一个来自S1,一个来自S2,那么p3和q3两者与m的距离都不超过dis(此处并不是不超过 dis/2 因为一个可能大于 dis / 2 一个小于 dis / 2 这样|p3-q3|的绝对值还有可能小于dis).那么就确定了这两个点的取值区间了.p3一定是(m-dis,m) q3一定是(m,m+dis),这样缩小了寻找的范围了.
下面推广到二维层面。



先看左图。同样是将S中的元素按照x坐标升序排列,分割成S1,S2。从S1中求出的最小点对距离为 : dis1 = |p1-p2|从S2中求出的最小点对距离为 : dis2 = |q1-q2|
求出dis = min(dis1,dis2)假设最小点对的是p3,q3,其中p3来自S1,q3来自S2,和一维的思想方案一样,p3一定在(m-dis,m) q3一定在(m,m+dis),在这个区间内,又有很多的候选结点,最坏情况下我们要遍历 n/2 * n/2 = n^2/4次,在数据规模大的情况下也是很头疼的一件事。换一种角度思考(二维最小点对问题的最重要的地方...)看上图的右边。假设S1中的是P点(因为遍历的时候先从左遍历..遇到的第一个就假设为P点)假设S2中的是Q点。因为PQ是最小点对,那么|PQ| < dis是妥了的,那么Q点一定是落在一个 d*2d这么大的一个区域中的(只有这样才能保证PQ<dis)事实上,刚才的说法很笼统。我们可以将矩形R的长为2dis的边3等分,将它的长为dis的边2等分,由此导出6个(dis/2)×(2dis/3)的矩形。
如图所示:


由此可以推出矩形R中最多只有6个S中的点(是S中的点,不是满足条件d(p,q)<dis q∈S2中的点,这一点记清楚)

若矩形R中有多于6个S中的点,则由鸽舍原理(抽屉原理)易知至少有一个dis×2dis的小矩形中有2个以上S(再次强调是总结合S)中的点。设u,v是这样2个点,它们位于同一小矩形中,则 因此d(u,v)<dis 。这与dis(最小距离)的意义相矛盾。也就是说矩形R中最多只有6个S中的点。图4(b)是矩形R中含有S中的6个点的极端情形。由于这种稀疏性质,对于P1中任一点p,P2中最多只有6个点与它构成最接近点对的候选者。因此,在分治法的合并步骤中,我们最多只需要检查6×n/2=3n对候选者,而不是n^2/4对候选者。我们现在知道对于每个S1中的点p最多只需要检查S2中的6个点(确切的说,是矩形R中的6个S中的点),但是我们并不确切地知道要如何检查。为了解决这个问题,我们可以将所有S2(属于(m,m+dis)区间的)的点投影到垂直线l上。由于能与p点一起构成最接近点对候选者的S2中点一定在矩形R中,并且要满足d(p,q)<dis q∈S2(同意这里的S2指的是(m,m+dis)区域),因此满足条件的点它们在直线l上的投影点距p在l上投影点的距离小于dis,由上面的分析可知,这种投影点不多于6个。因此,若将P1和P2中所有S的点按其y坐标排好序,则对P1中所有点p,对排好序的点列作一次扫描,就可以找出所有最接近点对的候选者,对P1中每一点最多只要检查P2中排好序的相继6个点。杭电亲测1260MS..(我觉得挺慢的,杭电最快0MS我是不知道怎么做的了..)
#include <cstdio>
#include <algorithm>
#include <cmath>
#define min(A,B) ((A) < (B) ? (A) : (B))
#define maxn 100005
#define INF  9999999.9
using std::sort;
struct toy{
double x,y;
}point[maxn];
int N;
int temp[maxn];

int cmp_xy(struct toy a,struct toy b)
{
if(a.x != b.x)
return a.x < b.x;
return a.y < b.y;
}
int cmp_y(int pre,int next)
{
return point[temp[pre]].y < point[temp[next]].y;
}
double distance(int i,int j)
{
double temp1 = point[i].x - point[j].x;
double temp2 = point[i].y - point[j].y;
return sqrt((temp1) * (temp1) + (temp2) * (temp2));
}

double closet_pair(int left,int right)
{
double dis = INF;
if(left == right)//当前集合只有一个坐标
return dis;
if(left + 1 == right)//当前集合只有两个坐标
return distance(left,right);
int mid = (left + right) / 2;
double dis1 = closet_pair(left,mid);//集合1
double dis2 = closet_pair(mid + 1,right);//集合2
/*两个集合分别递归求集合中的最小距离点对*/
dis = min(dis1,dis2);
/*接下来就应该求虚线区域(mid-dis,mid+dis)...*/
int count = 0 ;/*count代表在满足在虚线区域内的电究竟有几个*/
for(int i = left ; i <= right ; i++)
{
if(fabs(point[mid].x - point[i].x) < dis)
temp[count++] = i;
/*满足在虚线区域内的点是第几个点存到temp中*/
}
sort(temp,temp+count,cmp_y);
/*把在虚线区域内的坐标按照y升序排列*/
/*在区域(mid-dis,mid+dis)内枚举*/
for(int i = 0 ; i < count ; i++)
{
for(int j = i + 1 ; (j < count && point[temp[j]].y - point[temp[i]].y < dis) ; j++)
{
/*point[temp[j]].y - point[temp[i]].y > dis的点肯定不是我们要的*/
double dis3 = distance(temp[i],temp[j]);
if(dis3 < dis)
dis  = dis3;
}
}
return dis;
}

int main()
{
while(scanf("%d",&N) != EOF && N)
{
for(int i = 0 ; i < N ; i++)
scanf("%lf%lf",&point[i].x,&point[i].y);
sort(point,point+N,cmp_xy);
double radius = closet_pair(0,N-1) / 2;
printf("%.2lf\n",radius);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: