[POJ Challenge][BZOJ2289][JZOJ2277]圆,圆,圆

本文介绍了一种高效判断平面上多个圆是否存在面积非零交集的算法。通过确定有效的x坐标区间,并采用二分法查找可能的交点,最终在限定次数内判断交集的存在性。

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

题目大意

给定平面上n个圆(圆心(xi,yi),半径ri),你需要判断这些圆是否存在面积非零的交集。

1n105


题目分析

假设我们知道这个交集一定存在,那么我们现在要随便求交集上一点应该怎么做?
先根据每个圆左右两条卡住圆的垂线来确定出一个x坐标区间,使得该区间上所有点的竖直线都能和所有圆相交。如果区间不存在,那么显然无界。
然后在这个区间上我们要怎么做呢?考虑二分出这个点。先在之前求出的区间的中点作出一条竖直线,然后求出各个圆交在上面的y坐标区间。如果这些区间有交集,显然在存在一个交点其横坐标是与竖直线一致,纵坐标在这个y坐标区间内。否则的话我们随便找到两个不相交的区间(比如一个最左一个最右),然后计算它们所对应的圆的交点,显然这两个交点一定同时在竖直线的一侧,指引着我们交集在哪一侧,我们将l或者r调整过去就好了。如果无界的话就会出现这两个区间所对应的圆相离或者相切的情况,这个判掉就好了。
当然这个还是有可能存在一些其它的无解情况,毕竟我们并不能保证每次都找到矛盾的那对圆。怎么办呢?其实我们二分达到20次左右时,交集面积已经可以判定为0了,如果这时还没有判出合法,那就一定没有交集了。
时间复杂度O(nlogX)。其中X为坐标范围。


代码实现

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

using namespace std;

typedef long double db;

inline 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 int N=100050;
const db EPS=1e-9;

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

struct P
{
    db x,y;

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

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

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

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

inline P rotate(P p,db a){return P(p.x*cos(a)-p.y*sin(a),p.x*sin(a)+p.y*cos(a));}
inline P adjust(P p,db len){return p*(len/mod(p));}

inline 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];

inline void ict(C c,db x,P &p,P &q){p=adjust(P(x-c.O.x,0),c.r),x=acos(fabs(x-c.O.x)/c.r),q=c.O+rotate(p,x),p=c.O+rotate(p,-x);}

inline bool is_intersectant(C c1,C c2){return sgn(dist(c1.O,c2.O)-c1.r-c2.r)<0;}
inline void ict(C c1,C c2,P &p,P &q)
{
    db d=dist(c1.O,c2.O),a=acos((sqr(c1.r)+sqr(d)-sqr(c2.r))/(2.*c1.r*d));
    p=adjust(c2.O-c1.O,c1.r),q=c1.O+rotate(p,a),p=c1.O+rotate(p,-a);
}

db range[N][2];
int n;

bool judge()
{
    db l=-DBL_MAX,r=DBL_MAX;
    for (int i=1;i<=n;++i) l=max(cir[i].O.x-cir[i].r,l),r=min(cir[i].O.x+cir[i].r,r);
    if (sgn(l-r)>=0) return 0;
    for (int cas=0;cas<20;++cas)
    {
        db mid=(l+r)/2.;
        int L=0,R=0;
        for (int i=1;i<=n;++i)
        {
            P p,q;
            ict(cir[i],mid,p,q),range[i][0]=min(p.y,q.y),range[i][1]=max(p.y,q.y);
            if (!L||range[i][0]>range[L][0]) L=i;
            if (!R||range[i][1]<range[R][1]) R=i;
        }
        if (sgn(range[L][0]-range[R][1])<0) return 1;
        if (!is_intersectant(cir[L],cir[R])) return 0;
        P p,q;
        ict(cir[L],cir[R],p,q);
        if (sgn(mid-p.x)>0) r=mid;
        else l=mid;
    }
    return 0;
}

int main()
{
    freopen("circle.in","r",stdin),freopen("circle.out","w",stdout);
    n=read();
    for (int i=1,x,y,r;i<=n;++i) x=read(),y=read(),r=read(),cir[i]=C(P(x,y),r);
    printf(judge()?"YES\n":"NO\n");
    fclose(stdin),fclose(stdout);
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值