HDU 3124 Moonmist(平面最近圆对,二分+扫描线)

An Unidentified Flying Object (Commonly abbreviated as UFO) is the popular term for any aerial phenomenon whose cause cannot be easily or immediately identified. We always believe UFO is the vehicle of aliens. But there is an interrogation about why UFO always likes a circular saucer? There must be a reason. Actually our scientists are developing a new traffic system “Moonmist”. It is distinguished from the traditional traffic. We use circular saucers in this new traffic system and the saucers moves extremely fast. When our scientists did their test, they found that traffic accident was too hard to be avoided because of the high speed of the advanced saucer. They need us to develop a system that can tell them the nearest saucer. The distance between two saucers is defined as the shortest distance between any two points in different saucers.

Input

The first line consists of an integer T, indicating the number of test cases. 
The first line of each case consists of an integer N, indicating the number of saucers. Each saucer is represented on a single line, consisting of three integers X, Y, R, indicating the coordinate and the radius. You can assume that the distance between any two saucers will never be zero. 

Output

For each test case, please output a floating number with six fractional numbers, indicating the shortest distance. 
Constraints 
0 < T <= 10 
2 <= N <= 50000 
0 <= X, Y, R <= 100000 

Sample Input

1
2
0 0 1
10 10 1

Sample Output

12.142136

思路:首先先确定一个二分的思路,因为它给的是都不相交的圆,考虑扩大他们的半径,找到最优的使得圆都不相交的距离,就是最近的两个圆的距离。

其次,考虑如何在二分的时候判断是否有圆相交,如果普通的判断需要O(n^2),显然不 成立。

其实可以这样处理,从左往右扫过去,每扫一段,只要判断当前的圆与上下的圆是否相交就行,为了维护这样的性质,需要保证:分段时,L<R,因为只有这样,在当前段我们不需要考虑左右相交的情况。

还有就是,在L左边的圆,都可以排除,因为它必然已经参与过检验。

所以:运用扫描线的思路,设定两条扫描线,L,R,依次移动L,R,并且判断他们之间的圆的相交情况,这样就能在O(n)时间将圆都检查一遍。

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

如图:

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

         第一条扫描线从左往右依次是每个圆的左边界,即竖直线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 <bits/stdc++.h>
const int maxn=3e5+7;
#define mod 998244353
#define Lson l,m,rt<<1
#define Rson m+1,r,rt<<1|1
const long long inf=(long long)1e18;
const double eps=1e-8;
typedef long long ll;
using namespace std;
int n;
double x[maxn],y[maxn],r[maxn];
int L[maxn],R[maxn],up[maxn],up_rank[maxn];
double mid;
set<int> se;
typedef set<int>::iterator it;
inline double sqr2(double a)
{
    return a*a;
}
inline bool cross(int a,int b)//判断圆与圆是否相交
{
    a=up[a],b=up[b];
    double s1=sqr2(x[a]-x[b])+sqr2(y[a]-y[b]);
    double s2=sqr2(r[a]+r[b]+2*mid);
    return s1<=s2;
}
inline bool Insert(int a)//插入圆,同时进行检查
{
    it i=se.insert(a).first;//因为排序的原因,所以直接比较上下两个就行
    if(i!=se.begin())
    {
        if(cross(a,*--i)) return true;
        i++;
    }
    if(++i!=se.end())
    {
        if(cross(a,*i)) return true;
    }
    return false;
}
inline bool judge()
{
    se.clear();
    int i=1,j=1;
    while(i<=n||j<=n)
    {
        if(j==n+1||(i!=n+1&&(x[L[i]]-r[L[i]]-mid<x[R[j]]+r[R[j]]+mid)))
        {
            if(Insert(up_rank[L[i++]])) return true;
        }
        else
        {
            se.erase(up_rank[R[j++]]);//当移动R时,需要把之前的移除
        }
    }
    return false;
}
double solve()
{
    double ll,rr;
    ll=0,rr=sqrt(sqr2(x[1]-x[2])+sqr2(y[1]-y[2]))-r[1]-r[2];
    while(ll+eps<rr)
    {
        mid=(ll+rr)*0.5;
        if(judge())
        {
            rr=mid;
        }
        else
        {
            ll=mid+eps;
        }
    }
    return ll+rr;
}
int main()
{
    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
        freopen("out.txt","w",stdout);
    #endif
    int T;
    cin>>T;
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%lf%lf%lf",&x[i],&y[i],&r[i]);
        }
        for(int i=1;i<=n;i++)
        {
            L[i]=R[i]=up[i]=i;
        }
        sort(L+1,L+1+n,[&](int a,int b){return x[a]-r[a]<x[b]-r[b];});//给L扫描线排序
        sort(R+1,R+1+n,[&](int a,int b){return x[a]+r[a]<x[b]+r[b];});
        sort(up+1,up+1+n,[&](int a,int b){
            return y[a]==y[b]?x[a]<x[b]:y[a]<y[b];
        });//up[i]表示排第几的是谁
        for(int i=1;i<=n;i++)
        {
            up_rank[up[i]]=i;//它排第几
        }
        double ans=solve();
        printf("%.6f\n",ans);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值