加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

HDU 3124 二分 扫描线 最近圆对 (纠正小错误)

(2011-01-31 21:58:02)
标签:

hdu

3124

二分

扫描线

最近圆对

it

分类: 计算几何

题目描述:

给你n(50000)个不相交的圆,告诉你各个圆的xy坐标和半径,问你最近的两个圆之间的距离(两圆心之间的距离减去各自的半径)

解题报告:

         暴力的做法扫描任意两圆之间的最近距离,取最小值,n^2复杂度,超时。

         正解为:

二分枚举每个圆半径的增量mid。

         那么此时的每个圆i的半径为R[i] + mid(R[i]为这个圆原来的半径),对于这组新的半径,如果在n个圆中存在两个圆相交,那么就说明mid偏大,反之,如果不存在,则mid偏小。这样就可以达到二分的目的。

         二分的复杂度可以在此近似为logn,那么问题转化为:怎样在O(n)或者O(nlogn)的时间内检测出n个圆内是否存在相交的情况,答案是“扫描线”。

         常见的扫描线通常利用在正方形的各种检测和求和:在x方向从左到右扫描,y方向用set或者线段树维护当前的线段位置即可,如果覆盖相同的线段即相交。

         如下:

    http://s1/middle/64675f54t9b24d29d0530&6903124 二分 扫描线 最近圆对 (纠正小错误)" TITLE="HDU 3124 二分 扫描线 最近圆对 (纠正小错误)" />

从左到右依次扫描竖直的四条线段ABCD,

A:插入1~3的线段

B:插入0~2的线段,和现存的1~3线段冲突,所以存在相交情况。

C:删除1~3线段

D:删除0~2线段

         但是现在是圆形,从左到右扫描,一个圆的开始位置和结束位置之间,这个圆的纵向线段长度是在不断变化的,需要另外的一种方法。

         这里采用两条扫描线,均是从左向右,每次检测的是这两条扫描线之间的圆的碰撞情况。

如图:

http://s2/middle/64675f54t9b24d41fba11&6903124 二分 扫描线 最近圆对 (纠正小错误)" TITLE="HDU 3124 二分 扫描线 最近圆对 (纠正小错误)" />

         第一条扫描线从左往右依次是每个圆的左边界,即竖直线L1,L2,L3。

         第二条扫描线从左往右依次是每个圆的右边界,即竖直线R1,R2,R3。

         两条扫描线均是从最左边的L1和R1开始,保证L扫描线的x坐标永远小于R扫描线的x坐标即可。扫描流程如下,假设有n个圆,i为Li,j为Rj:

         i = j = 1

         while(i <= n or j <= n)

         {

                   if(i == n + 1) 删除圆j,j++;

                   else if (j == n + 1) 插入圆i,检测圆i的圆心和y方向相邻两圆的碰撞情况。i++。

                   else if (i <= n and Li 在Ri的左边)

                            插入圆i,检测圆i的圆心和y方向相邻两圆的碰撞情况。i++。

                   else 删除圆j,j++;

         }

简要解释如下:

While中有4个分支

前两个是边界情况,很容易理解。

第三个是L扫描线的推进,即插入圆。

第四个条件:由于只需检测Li 和 Rj两条扫描线之间的圆的相交情况,所以,Li之前的圆都需要删除

当扫描线为Li和Rj时,已经插入的圆都是x方向起点小于等于Li,x方向终点大于等于Rj,即在Li和Rj之间是连续不间断的,所以只需检测插入圆的圆心的上下相邻的两个即可,不可能跳跃。即假设圆心位置从下到上一次编号1~m,那么插入圆x后,不可能出现x和x+2相交而不和x+1相交的情况(应为在Li和Rj之间是连续的)。

至于检测的方法就是线段树或者set了。

上图的扫描线过程是:

初始为L1,R1

L1 < R1, 插入圆A,检测无碰撞,变为L2,R1

L2 < R1, 插入圆B,检测无碰撞,变为L3, R1

L3 > R1, 删除圆B,变为L3,R2

L3 < R2,插入圆C,检测到碰撞,结束

 

代码如下:

#include<iostream>

#include<cstdio>

#include<cstring>

#include<set>

#include<algorithm>

#include<cmath>

using namespace std;

#define size 50000

#define eps 1e-8

#define sqr(x) ((double)(x) * (x))

int Left[size], Right[size], Up[size], up_rank[size];

int X[size], Y[size], R[size];

int t, n;

double mid;

set<int> mytree;

typedef set<int>::iterator it;

bool cmp_left(const int &a, const int &b){return (X[a] - R[a] < X[b] - R[b]);}

bool cmp_right(const int &a, const int &b){return (X[a] + R[a] < X[b] + R[b]);}

bool cmp_up(const int &a, const int &b){if (Y[a] == Y[b]) return X[a] < X[b]; else return Y[a] < Y[b];}

bool collid(int a, int b)

{

    a = Up[a];

    b = Up[b];

    return (sqr(X[a] - X[b]) + sqr(Y[a] - Y[b]) - sqr(R[a] + R[b] + mid + mid) <= 0);

}

bool insert(int &id)

{

    it i = mytree.insert(id).first;

    if (i != mytree.begin())

    {

        if (collid(id, *--i))

            return false;

        ++i;

    }

    if (++i != mytree.end())

    {

        if (collid(id, *i))

            return false;

    }

    return true;

}

bool remove(int v) {

    it i = mytree.find(v);

    if (i != mytree.begin() && i != --mytree.end()) {

        int a = *--i;

        ++i;

        int b = *++i;

        if (collid(a, b)) {

            return false;

        }

    }

    mytree.erase(v);

    return true;

}


bool judge()

{

    mytree.clear();

    int l = 0, r = 0;

    while(l < n || r < n)

    {

        if (r == n

        ||l != n && (X[Right[r]] + R[Right[r]] + mid) - (X[Left[l]] - R[Left[l]] - mid) >= 0)

        {

            if (!insert(up_rank[Left[l++]])) return true;

        }

        else if (!remove(up_rank[Right[r++]]))

            return true;

    }

    return false;

}

double jeogia()

{

    for(int i = 0; i < n; i++)

        Left[i] = Right[i] = Up[i] = i;

    sort(Left, Left + n, cmp_left);

    sort(Right, Right + n, cmp_right);

    sort(Up, Up + n, cmp_up);

    for(int i = 0; i < n; i++)

        up_rank[Up[i]] = i;

    double s = 0, t = sqrt(sqr(X[0] - X[1]) + sqr(Y[0] - Y[1])) - R[0] - R[1];

    while(t - s >= eps)

    {

        mid = (s + t) * 0.5; //mid 为各个圆需要加上的半径长度

        if (judge()) t = mid;//是否有圆相交

        else s = mid;

    }

    return t + s;

}

int main()

{

    scanf("%d", &t);

    while(t-- && scanf("%d", &n))

    {

        for(int i = 0; i < n; i++)

            scanf("%d%d%d", &X[i], &Y[i], &R[i]);

        printf("%.6f\n", jeogia());

    }

    return 0;

}





 

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有