[BZOJ4561][JLoi2016]圆的异或并(扫描线+splay)

211 篇文章 0 订阅
55 篇文章 0 订阅

题目描述

传送门

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

题解

这题并没有做出来。。想了很多很多种不科学的方法。。
首先对于一个圆来说,可以将其分成上半个圆弧和下半个圆弧,并且在扫描整个圆的过程中(对于每一个横坐标),两个圆弧的相对位置不变。对于圆之间的相对包含关系,可以搞成一棵树(森林),奇数层的面积加偶数层的面积减就行了。
将每一个圆搞出左边界和右边界,然后按照x坐标排序做扫描线。遇到一个圆的左边界,在splay中插入两个点,分别表示这个圆的上半弧和下半弧。插入的时候需要利用圆的解析式计算一下之前splay中的点在当前横坐标x时的纵坐标y来判断插入位置。
插入之后找一下刚插入的上半弧的前驱。如果前驱是某一个圆的上半弧说明这个圆就是最小的包含当前圆的圆,如果是下半弧说明当前圆和这个圆的被包含关系相同,如果没有前驱则说明这个圆没有被任何圆包含。
遇到圆的右端点把两个圆弧都从splay里删除掉。
这样就做完了

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 400005

int n,cnt,root,sz;long long ans;
struct C{int x,y,r;}c[N];
struct OPT{int x,id,val;}opt[N];
int f[N],ch[N][2],type[N],id_in_graph[N],id_in_splay[N][2],h[N];

int cmp(OPT a,OPT b){return a.x<b.x||(a.x==b.x&&a.val<b.val);}
int get(int x)
{
    return ch[f[x]][1]==x;
}
void rotate(int x)
{
    int old=f[x],oldf=f[old],wh=get(x);
    ch[old][wh]=ch[x][wh^1];
    if (ch[old][wh]) f[ch[old][wh]]=old;
    ch[x][wh^1]=old;
    f[old]=x;
    if (oldf) ch[oldf][ch[oldf][1]==old]=x;
    f[x]=oldf;
}
void splay(int x)
{
    for (int fa;(fa=f[x]);rotate(x))
        if (f[fa])
            rotate((get(x)==get(fa))?fa:x);
    root=x;
}
int compare(int ty,int id,double x,double y)
{
    double a=c[id].x,b=c[id].y,r=c[id].r;
    double z;
    if (!ty) z=sqrt(r*r-(x-a)*(x-a))+b;
    else z=-sqrt(r*r-(x-a)*(x-a))+b;
    return z>=y;
}
void insert(int id)
{
    if (!root)
    {
        root=++sz;
        type[sz]=0;
        id_in_splay[id][0]=sz;
        id_in_graph[sz]=id;

        f[++sz]=sz-1;ch[sz-1][1]=sz;type[sz]=1;
        id_in_splay[id][1]=sz;
        id_in_graph[sz]=id;
        return;
    }
    int now=root,fa=0,val=c[id].y;
    while (1)
    {
        int wh=compare(type[now],id_in_graph[now],c[id].x-c[id].r,c[id].y);
        fa=now;
        now=ch[now][wh];
        if (!now)
        {
            ch[fa][wh]=++sz;f[sz]=fa;type[sz]=0;
            id_in_splay[id][0]=sz;
            id_in_graph[sz]=id;
            ch[sz][1]=sz+1;f[sz+1]=sz;type[++sz]=1;
            id_in_splay[id][1]=sz;
            id_in_graph[sz]=id;
            splay(sz-1);
            return;
        }
    }
}
void del(int x)
{
    splay(x);int oldroot=root,leftbig;
    if (!ch[root][0]&&!ch[root][1])
    {
        root=0;
        return;
    }
    if (!ch[root][0])
    {
        root=ch[oldroot][1];
        f[root]=0;
        return;
    }
    if (!ch[root][1])
    {
        root=ch[oldroot][0];
        f[root]=0;
        return;
    }
    leftbig=ch[root][0];
    while (ch[leftbig][1]) leftbig=ch[leftbig][1];
    splay(leftbig);
    ch[root][1]=ch[oldroot][1];
    f[ch[root][1]]=root;
    return;
}
int findpre()
{
    int x=ch[root][0];
    if (!x) return -1;
    while (ch[x][1]) x=ch[x][1];
    return x;
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;++i)
    {
        scanf("%d%d%d",&c[i].x,&c[i].y,&c[i].r);
        opt[++cnt].x=c[i].x-c[i].r,opt[cnt].id=i,opt[cnt].val=1;
        opt[++cnt].x=c[i].x+c[i].r,opt[cnt].id=i,opt[cnt].val=-1;
    }
    sort(opt+1,opt+cnt+1,cmp);
    for (int i=1;i<=cnt;++i)
    {
        int id=opt[i].id;
        if (opt[i].val==-1)
        {
            del(id_in_splay[id][0]);
            del(id_in_splay[id][1]);
        }
        else
        {
            insert(opt[i].id);
            int pre=findpre();
            if (pre==-1) h[id]=1;
            else
            {
                if (type[pre]==0) h[id]=h[id_in_graph[pre]]+1;
                else h[id]=h[id_in_graph[pre]];
            }       
            if (h[id]&1) ans+=(long long)c[id].r*c[id].r;
            else ans-=(long long)c[id].r*c[id].r;
        }
    }
    printf("%lld\n",ans);
}

感谢ATP的数据生成器

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<ctime>
#define random(x)(rand()*rand()%x+1)
using namespace std;
int n,X[10010],Y[10010],R[10010];
int getrand(int lim){
    return random(lim)-random(lim);
}
double Abs(double x){return (x<0)?-x:x;}
bool check(int x,int y,int r,int id){
    double dx=x-X[id],dy=y-Y[id];
    dx=sqrt(dx*dx+dy*dy);
    if (Abs(r-R[id])<=dx&&Abs(r+R[id])>=dx) return true;
    return false;
}
int main()
{
    freopen("input.in","w",stdout);
    n=100;srand(time(0));
    printf("%d\n",n);
    for (int i=1;i<=n;){
        int x,y,r;
        bool flag=false;
        x=getrand(100);y=getrand(100);r=random(100);
        for (int j=1;j<i;j++)
          if (check(x,y,r,j)){
              flag=true;break;
          }
        if (flag==true) continue;
        X[i]=x;Y[i]=y;R[i]=r;i++;
    }
    for (int i=1;i<=n;i++)
      printf("%d %d %d\n",X[i],Y[i],R[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值