hdu 1007 zoj 2107 Quoit Design 求平面最近点对 分治法

博客已经搬家,新链接:点击打开链接

本题题目很裸,只要求出平面中最近点对的距离,然后除以2就是答案,方法就是分治,具体的可以参见算法导论

简单分析:对于该问题的一个子问题,只需把当前点的集合再分成两部分,分的方法是:按照x(或者y)排序,然后分成了前一半和后一半,即相当于在这些点的中间做了一条垂线,然后算垂线左边的最近点对(设距离为d1),垂线右边的最近点对(设距离为d2),那么在当前情况下,最近点对可能出自左半部分的子问题,也可能是右半部分,还可能是左边一个点,右边一个点的那种,对于前两种情况,已经算得d1,d2,比个大小就好了,对于第三种情况,则需再算,设d=min(d1,d2),那么第三种情况除非能找到小于d的点对,不然是没有意义的。如果左右两方的点对要小于d,那么前提就是左边的点到那条垂线(刚才分左右的那条线)的距离小于d,右边同理(这个很容易想象)……这样一来,点的数量就会大大减少……当然光这样也是不够的。我们需要对左边那部分的点的Y坐标排序,对右边也是,但是这里用不着每次都O(nlogn)的去快排一次,只要在最初的时候把所有的点快排完,存在数组Y中,然后每次左右分完之后,只需按X的顺序扫一遍相应的Y数组,如果扫到的点在左办边,就把Y[i]赋值到左半边,右边亦然,这样肯定能保证,左右两半的Y都是有序的。取出Y中距离垂线距离小于d的所有点,然后对这些点暴力,因为Y是有序的,所以可以有效利用这个条件,每个点只要和之后的5个点算一下就好了,为什么呢?


假设当前扫到了点P(假设在左边),那么点P以下的点其实都不用考虑了,因为已经算过了,对点P作这样一个2d*d的矩形,如果要找到与点P距离小于d的点,那么右边那个点必须落在右边那个d*d的矩形中……同理当P在右边时也成立。又左半边所有的点的距离不小于d,所以左半边那个d*d的矩形中最多放4个点(这个很直观,当然也需要证明过,在此详细说了);右边亦然…………而如果真的要做到的话,垂线上必须放两个,即左右有重复算的(这在分完左右两边之后是不会发生的,因为右边的点其实不会在垂线上)…………总之,不严密的可以看出,对P可能形成最近点对的点最多只有他后面的5个(前面的点之前已经算完了)…………这样对每个点枚举五次就好了…………

以下是代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define esp 1e-6
using namespace std;
struct point
{
    double x,y;
}p[100010];
int px[100010],py[100010],ty[100010];
int cmpx(const point &a,const point &b)
{
    return a.y<b.y;
}
int cmpy(const int &a,const int &b)
{
    return p[a].x<p[b].x;
}
inline double min(double a,double b)
{
    return a<b?a:b;
}
inline double dist2(point &a,point &b)
{
    return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
double min_dis = 1e100;
double mindist(int* X,int *Y,int size)
{
    if(size<=3)
    {
        if(size==2)
        return dist2(p[X[0]],p[X[1]]);
        for(int i=0; i<size; i++)
        min_dis = min(min_dis,dist2(p[X[i]],p[X[(i+1)%size]]));
        return min_dis;
    }
    int pl = (size+1)/2;
    int pr = size - pl;
    int l1 = 0,l2=pl;
    for(int i=0; i<size; i++)
    if(Y[i]<X[pl])
    ty[l1++] = Y[i];
    else ty[l2++] = Y[i];
    for(int i=0; i<size; i++)
    Y[i] = ty[i];
    min_dis = min(mindist(&X[0],&Y[0],pl),mindist(&X[pl],&Y[pl],pr));
    l1 = 0;
    for(int i=0; i<size; i++)
    if((p[Y[i]].x-p[X[pl-1]].x)*(p[Y[i]].x-p[X[pl-1]].x)<=min_dis)
    ty[l1++] = Y[i];
    for(int i=0; i<l1; i++)
    for(int j=1; j<6&&i+j<l1; j++)
    if((ty[i]-X[pl])*(ty[i+j]-X[pl])<=0)
    min_dis = min(min_dis,dist2(p[ty[i]],p[ty[i+j]]));
    return min_dis;
}

int main()
{
    int n;
    for(int i=0; i<100010; i++)
    px[i] = i;
    while(scanf("%d",&n)&&n)
    {
        for(int i=0; i<n; i++)
        {
            scanf("%lf%lf",&p[i].x,&p[i].y);
            py[i] = i;
        }
        sort(p,p+n,cmpx);
        sort(py,py+n,cmpy);
        min_dis = 1e100;
        printf("%.2lf\n",sqrt(mindist(px,py,n))/2);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值