[HDU3124]Moonmist

本文介绍了一种解决最近圆对距离问题的有效算法。通过二分查找确定最小距离,并利用竖直扫描线技巧配合数据结构来高效判断圆之间的相交状态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

给定平面内n个圆Ci(圆心Oi(xi,yi),半径ri)。
定义两个圆Ci,Cj的距离为|OiOj|rirj。求最近圆对的距离。

一个测试点T组数据。
0<T10,2n5×104,0xi,yi,ri105


题目分析

考虑二分这个距离d,怎么检验这个距离呢?
将每个圆半径扩展d/2的长度,如果存在两个圆相交,那么就说明答案比这个距离短。
现在问题变成怎么快速判断是否存在相交的圆。
考虑将所有圆分成左右两条竖直扫描线。我们按照x坐标顺序一一枚举这些扫描线。假设我们当前枚举到的最后一条左扫描线是,那么我们考虑左边的所有左扫描线所属的圆的右扫描线能够成的最小合法区间,使用数据结构维护完全跨立这个区间的所有圆。可以证明,任意一对相交的圆一定能够同时出现在某一个这一种区间内,并且在所有完全跨立这个区间的圆中,这两个圆的圆心的y坐标相邻。
那么现在算法流程就很清晰了。
考虑枚举所有扫描线,如果是左扫描线,就加入圆并且检查与其圆心y坐标相邻的两个圆是否与其相交。如果是右扫描线,就删除圆,并且检查删除的圆与原先两个圆心y坐标相邻的圆是否相交。
无论是加入还是删除都要检查相交情况,因为删除圆会使原先在加入时(圆心y坐标)不相邻的圆相邻。网上有的题解只考虑了加入圆,是可以用很简单的数据hack掉的。
这个数据结构使用set就好了。
总时间复杂度O(nlogXlogn)


代码实现

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>
#include <cmath>
#include <set>

using namespace std;

typedef long double db;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const db EPS=1e-9;
const int N=50005;

bool equ(db x,db y){return fabs(x-y)<=EPS;}
int sgn(db x){return equ(x,0)?0:(x<0?-1:1);}
db sqr(db x){return x*x;}

struct P
{
    db x,y;

    P (db x_=0,db y_=0){x=x_,y=y_;}
};

P operator+(P p,P q){return P(p.x+q.x,p.y+q.y);}
P operator-(P p,P q){return P(p.x-q.x,p.y-q.y);}
P operator*(P p,db k){return P(p.x*k,p.y*k);}

db operator*(P p,P q){return p.x*q.x+p.y*q.y;}
db operator^(P p,P q){return p.x*q.y-p.y*q.x;}

db mod2(P p){return p*p;}
db mod(P p){return sqrt(mod2(p));}

db dist(P p,P q){return mod(q-p);}

struct C
{
    P O;
    db r;

    C (){}
    C (P O_,db r_=0){O=O_,r=r_;}
}cir[N],c[N];

bool is_intersectant(C c1,C c2){return sgn(c1.r+c2.r-dist(c1.O,c2.O))>=0;}

int l[2][N],RANK[N],KTH[N];
int T,n;
db X[2],Y[2];

struct cid
{
    int id;

    cid (int id_=0){id=id_;}

    bool operator<(const cid &y)const{return RANK[id]<RANK[y.id];}
};

bool cmp(int x,int y){return sgn(c[x].O.y-c[y].O.y)<0;}

set<cid> t;

db left(int x){return cir[x].O.x-cir[x].r;}
db right(int x){return cir[x].O.x+cir[x].r;}

bool cmp1(int x,int y){return sgn(left(x)-left(y))<0;}
bool cmp2(int x,int y){return sgn(right(x)-right(y))<0;}

bool erase_circle(int x)
{
    set<cid>::iterator it=t.find(cid(x));
    if (it!=t.begin())
    {
        --it;
        if (is_intersectant(cir[(*it).id],cir[x])) return 1;
        ++it;
    }
    if ((++it)!=t.end()&&is_intersectant(cir[(*it).id],cir[x])) return 1;
    return t.erase(--it),0;
}

bool insert_circle(int x)
{
    set<cid>::iterator it=t.insert(cid(x)).first;
    if (it!=t.begin())
    {
        --it;
        if (is_intersectant(cir[(*it).id],cir[x])) return 1;
        ++it;
    }
    if ((++it)!=t.end()&&is_intersectant(cir[(*it).id],cir[x])) return 1;
    return 0;
}

bool judge(db d)
{
    t.clear();
    for (int i=1;i<=n;++i) cir[i]=C(c[i].O,c[i].r+d),l[0][i]=l[1][i]=i;
    sort(l[0]+1,l[0]+1+n,cmp1),sort(l[1]+1,l[1]+1+n,cmp2);
    t.insert(l[0][1]);
    for (int lcur=2,rcur=1;lcur<=n||rcur<=n;)
        if (lcur==n+1||sgn(left(l[0][lcur])-right(l[1][rcur]))>0)
        {
            if (erase_circle(l[1][rcur++])) return 1;
        }
        else if (insert_circle(l[0][lcur++])) return 1;
    return 0;
}

db binary_search()
{
    db ret=0.;
    for (db l=0.,r=dist(P(X[0],Y[0]),P(X[1],Y[1])),mid;sgn(r-l)>=0;)
    {
        mid=(l+r)/2.;
        if (judge(mid)) r=mid-EPS;
        else l=(ret=mid)+EPS;
    }
    return ret*2.;
}

int main()
{
    freopen("moonmist.in","r",stdin),freopen("moonmist.out","w",stdout);
    for (T=read();T--;)
    {
        n=read();
        for (int i=1,x,y,r;i<=n;++i) x=read(),y=read(),r=read(),c[i]=C(P(x,y),r),KTH[i]=i;
        sort(KTH+1,KTH+1+n,cmp),X[0]=X[1]=c[1].O.x,Y[0]=Y[1]=c[1].O.y;
        for (int i=1;i<=n;++i) RANK[KTH[i]]=i,X[0]=min(X[0],c[i].O.x),Y[0]=min(Y[0],c[i].O.y),X[1]=max(X[1],c[i].O.x),Y[1]=max(Y[1],c[i].O.y);
        printf("%.6lf\n",(double)binary_search());
    }
    fclose(stdin),fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值