bzoj 4561: [JLoi2016]圆的异或并 (计算几何+扫描线+splay)

题目描述

传送门

题目大意:在平面直角坐标系中给定N个圆。已知这些圆两两没有交点,即两圆的关系只存在相离和包含。求这些圆的异或面积并。异或面积并为:当一片区域在奇数个圆内则计算其面积,当一片区域在偶数个圆内则不考虑。

题解

首先两个圆的相对位置是不会改变的。
这里写图片描述
上图中的三个圆与三条直线相交会形成上下两个交点,如果把交点看成是左右括号,那么括号序列的相对关系是不变的(如果存在同一条直线上),靠上的两个圆如果同时与其中一条直线相交那么必然是包含的关系。
我们将每个圆看成是一对括号,那么对于扫描到的位置其实就是一段合法的括号序列,我们用splay来维护他,splay 按照与直线交点的纵坐标有序。一个圆插入他的上端点(左括号),如果他的前驱也是左括号,那么他被覆盖的次数就是前一个的答案+1,如果是右括号,那么与前一个是并列的关系,所有与前一个的答案相同。
对于一个圆我们在扫描到x-r的时候加入,x+r的时候删除。
那么最终的答案就是被覆盖奇数次的圆的面积-被覆盖偶数次的圆的面积。

代码

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdio>
#define N 500003
#define inf 1000000000
#define LL long long 
using namespace std;
int l[N],r[N],val[N],opt[N],num[N],ch[N][3],fa[N],n,root,sz,cnt;
double T;
struct data{
  double x;
  int id,val;
  data(double X=0,int I=0,int V=0) {
    x=X,id=I,val=V;
  }
}q[N];
struct cir{
    double x,y,r;
}c[N];
int cmp(data a,data b)
{
    return a.x<b.x;
}
int get(int x)
{
    return ch[fa[x]][1]==x;
}
void rotate(int x)
{
    int y=fa[x]; int z=fa[y]; int which=get(x);
    ch[y][which]=ch[x][which^1]; fa[ch[x][which^1]]=y;
    if (z) 
     ch[z][ch[z][1]==y]=x;
    fa[x]=z; ch[x][which^1]=y; fa[y]=x;
}
void splay(int x,int tar)
{
    for (int f;(f=fa[x])!=tar;rotate(x))
     if (fa[f]!=tar)
      rotate(get(x)==get(f)?f:x);
    if (!tar) root=x;
}
int pre()
{
    int now=ch[root][0];
    while (ch[now][1]) now=ch[now][1];
    return now;
}
int next()
{
    int now=ch[root][1];
    while (ch[now][0]) now=ch[now][0];
    return now;
}
double pow(double x)
{
    return x*x;
}
void insert(int now,int h,double v)
{
    if(!root) {
        ++sz; root=sz; val[sz]=0;
        opt[sz]=1; num[sz]=0;
        return;
    }
    int x=root;
    while (true)
    {
        int t=val[x]; double vk=0;
        if (!t) vk=(opt[x]==1)?-inf:inf;
        else {
            double xx=c[t].y-sqrt(pow(c[t].r)-pow(c[t].x-T));
            double yy=c[t].y+sqrt(pow(c[t].r)-pow(c[t].x-T));
            vk=(opt[x]==1)?xx:yy;
        }
        int f=x;
        x=ch[x][v>=vk];
        if (!x) {
            ++sz; fa[sz]=f; ch[f][v>=vk]=sz;
            val[sz]=now; opt[sz]=h;
            if (h==1) {
                splay(sz,0); l[now]=sz;
                int pr=pre();
                num[sz]=(opt[pr]==1)?num[pr]+1:num[l[val[pr]]];
            }else r[now]=sz;
            break;
        }
    }
}
void del(int x)
{
    splay(x,0);
    int a=pre(); int b=next();
    splay(a,0); splay(b,a);
    ch[ch[root][1]][0]=0;
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("my.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;i++) {
      scanf("%lf%lf%lf",&c[i].x,&c[i].y,&c[i].r);
      q[++cnt]=data(c[i].x-c[i].r,i,1);
      q[++cnt]=data(c[i].x+c[i].r,i,-1);
    }
    sort(q+1,q+cnt+1,cmp);
    insert(0,1,-inf); insert(0,2,inf);
    LL ans=0;
    for (int i=1;i<=cnt;i++) {
        T=q[i].x; int now=q[i].id;
        if (q[i].val==1) insert(now,1,c[now].y),insert(now,2,c[now].y);
        if (q[i].val==-1){
         del(l[now]),del(r[now]);
         if (num[l[now]]&1) ans+=pow(c[now].r);
         else ans-=pow(c[now].r);
        }
    }  
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值