【BZOJ2961】共点圆(CDQ分治)

题面

BZOJ

题解

设询问点 (x,y) ( x , y ) ,圆心是 (X,Y) ( X , Y )
那么如果点在园内的话就需要满足
(Xx)2+(Yy)2X2+Y2 ( X − x ) 2 + ( Y − y ) 2 ≤ X 2 + Y 2
拆开之后就变成了
x2+y22xX2yY x 2 + y 2 − 2 x X ≤ 2 y Y
除过去就是 xyX+x2+y22yY − x y X + x 2 + y 2 2 y ≤ Y
显然左边是一个直线,那么,这个式子的含义就是,
对于任意 (X,Y) ( X , Y ) ,在 X X 处的函数值都要小于Y
即这个直线在所有的圆心下方。
那么维护一下下凸壳,每次拿斜率去切凸壳,检查一下截距就好了。
当然,上面是假装 y>0 y > 0 ,如果 y<0 y < 0 的话需要变号,变成了所有圆心都在直线的下方了
这里需要维护一个上凸壳。
CDQ C D Q 分治就可以很好的维护这些东西了。
然而我这个傻逼这都不会写

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define ll long long
#define MAX 500500
#define Sqr(x) ((x)*(x))
const double eps=1e-8;
struct Opt{int op,id;double x,y,k;}p[MAX],tmp[MAX],S1[MAX],S2[MAX];
bool operator<(Opt a,Opt b){return a.k<b.k;}
bool cmp(Opt a,Opt b){return a.id<b.id;}
double Slope(Opt a,Opt b)
{
    if(fabs(a.x-b.x)<eps)return a.y<b.y?1e18:-1e18;
    return (a.y-b.y)/(a.x-b.x);
}
double Dis(Opt a,Opt b){return sqrt(Sqr(a.x-b.x)+Sqr(a.y-b.y));}
bool ans[MAX];
int n;
void CDQ(int l,int r)
{
    if(l==r)return;
    int mid=(l+r)>>1,t1=l,t2=mid+1,tp1=0,tp2=0;
    for(int i=l;i<=r;++i)
        if(p[i].id<=mid)tmp[t1++]=p[i];
        else tmp[t2++]=p[i];
    for(int i=l;i<=r;++i)p[i]=tmp[i];
    CDQ(l,mid);
    for(int i=l;i<=mid;++i)
    {
        if(p[i].op)continue;
        while(tp1>1&&Slope(S1[tp1-1],p[i])+eps>Slope(S1[tp1-1],S1[tp1]))--tp1;S1[++tp1]=p[i];
        while(tp2>1&&Slope(S2[tp2-1],p[i])-eps<Slope(S2[tp2-1],S2[tp2]))--tp2;S2[++tp2]=p[i];
    }
    t1=tp1,t2=1;
    for(int i=mid+1;i<=r;++i)
    {
        if(!p[i].op)continue;
        if(p[i].y<0)
        {
            while(t1>1&&Slope(S1[t1-1],S1[t1])<p[i].k)--t1;
            if(t1>0&&Dis(S1[t1],S1[0])<Dis(S1[t1],p[i]))ans[p[i].id]=false;
        }
        else
        {
            while(t2<tp2&&Slope(S2[t2],S2[t2+1])<p[i].k)++t2;
            if(t2<=tp2&&Dis(S2[t2],S2[0])<Dis(S2[t2],p[i]))ans[p[i].id]=false;
        }
    }
    CDQ(mid+1,r);
    t1=l;t2=mid+1;
    for(int i=l;i<=r;++i)
        if(t2>r||(t1<=mid&&p[t1].x<p[t2].x))tmp[i]=p[t1++];
        else tmp[i]=p[t2++];
    for(int i=l;i<=r;++i)p[i]=tmp[i];
}
int main()
{
    freopen("2961.in","r",stdin);
    freopen("2961.out","w",stdout);
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1,opt,sum=0;i<=n;++i)
    {
        double x,y;
        cin>>opt>>x>>y;
        if(opt==0)p[i]=(Opt){0,i,x,y,0},++sum;
        else p[i]=(Opt){1,i,x,y,0};
        if(fabs(y)>eps)p[i].k=-x/y;else p[i].k=1e18;
        if(opt==1)ans[i]=(bool)sum;
    }
    sort(&p[1],&p[n+1]);CDQ(1,n);
    sort(&p[1],&p[n+1],cmp);
    for(int i=1;i<=n;++i)if(p[i].op)puts(ans[i]?"Yes":"No");
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值