hud 1007 Quoit Design 求解平面最近点对的问题

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

所谓最近点对呢,就是平面上给你一堆点,然后求出这堆点中相距最小的距离。

假如数据量比较小的话,那肯定是枚举所有点对求出各自距离再比较更方便了。很明显,枚举的时间复杂度是O(n²)。

不过一般不会给你这样的问题,点的个数往往很多,那就必然不能用枚举了。

这里要使用一种O(nlog(n))的算法。

这个算法主要思想就是分治,

算法描述:已知一个点的集合S,将S拆分成左右两部分求最近点对。首先对点集S进行排序,一般对点的X坐标从小到大排。算法每次选取一条直线(垂线)L,将S拆分成左右两部分Sl,Sr。L一般取点集S中所有点的中间点的x坐标来划分,这样可以保证SL和SR中的点数目各为n/2,否则,选取其他直线L划分可能导致Sl中有1个点,Sr中有n-1个点,这样算法的效率就很难保证了。依次找到Sl,Sr中的最短距离dl,dr。记录最短距离d=min(dl,dr);

如图:
这里写图片描述
很明显,只计算出Sl,Sr中的最短距离是不够的,S的最近点对很可能分别存在于Sl,Sr中。这种情况就要特殊处理了。

当然,只有在上图中两虚线之间的点才有再计算的意义,因为在这两条线之外的点与另一方的点的距离必然大于d。

对于Sl虚框范围内的某点P,在Sr虚框中与P点距离小于d的点顶多只有六个,就是图二右图中的2个正方形的6的顶点。

反证:假设Sr中存在一点Q与P的距离小于d,那么它与这个正方形的顶点的距离必然小于d,这与之前Sl,Sr中最短距离为d矛盾。

因此,对于Sl中的点P,不需要求出它与Sr中所有点的距离进行比较,根据鸽巢原理,只需要比较与P点Y坐标距离最近的6个点即可。这样大大降低了比较的次数。

求出Sl,Sr交界处的最短距离后对d进行更新即可。
这里写图片描述

算法时间复杂度: 首先对点集S的点x坐标和y坐标进行升序排序(快排),需要2nlogn次,

复杂度为O(2nlogn)在分治过程中,对于每个S’l中的点,都需要与S’r中的6个点进行比较,

复杂度为 O(n)。所以总的时间复杂度为O(nlogn),比起暴力枚举要好很多。

代码如下

//HDU 1007,代码模仿某位大神写的...Orz
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;
#define  MAXX 1<<30

struct point
{
    double x,y;
};
point p[100010];
int t[1000010];
double min(double a,double b)
{
    return a>b?b:a;
}

int cmp(const point &a,const point &b)         //对于点的x坐标排序;
{
    if(a.x==b.x) return a.y<b.y;
    return a.x>b.x;
}

int cmpy(const int &a,const int &b)    //对点的y坐标排序;
{
    return p[a].y<p[b].y;
}

double dis(point a,point b)
{
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

double findClosepari(int left,int right)                //分治,递归。两部分中间的情况要特殊处理;
{
    double x=MAXX;
    if(left==right) return x;
    if(left==right-1) return dis(p[left],p[right]);
    int mid=(left+right)>>1;                      //求出中间点;
    double dl=findClosepari(left,mid);
    double dr=findClosepari(mid+1,right);
    dl=min(dl,dr);

    int i,j,k;                                 //下面把中间2dl宽度的部分的点单独处理;
    k=0;
    for(i=left;i<=right;i++)
        if(fabs(p[i].x-p[i+1].x)<dl) t[k++]=i;

    sort(t,t+k,cmpy);   //由下到上排序,扫描;
    for(i=0; i<k; i++)
        for(j=i+1; j<k&&p[t[j]].y-p[t[i]].y<dl; j++)
        {
            double d3 = dis(p[t[i]],p[t[j]]);
            dl=min(dl,d3);
        }
    return dl;
}


int main()
{
    int n;
    while(cin>>n,n)
    {
        for(int i=1;i<=n;i++)
            scanf("%lf%lf",&p[i].x,&p[i].y);
        sort(p+1,p+n-1,cmp);
        double r=findClosepari(1,n)/2.0;
        printf("%.2lf\n",r);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值