洛谷 P3268 [JLOI2016]圆的异或并

洛谷 P3268 [JLOI2016]圆的异或并

题目描述

在平面上有两两不相交的\(n\)个圆,即其关系只有相离和包含。求这些圆的异或面积并。

异或面积并为:当一片区域被奇数个圆包含则计算其面积,否则不计算。

输出所有圆的异或面积并除以\(\pi\)的结果。

\(n\le 200000\)

Solution

前置知识:扫描线,set。

可以发现,由于圆是不相交的,那么这种包含关系可以看作是一棵森林(许多有根树构成的图)。

比如像这个几个圆

image-20210301105007886

可以变成

image-20210301105124990

设每个根的深度为1,那么深度为奇数的节点的面积是需要加上的,深度为偶数的节点的面积是需要减去的。

\[ans=\sum_{i=1}^nr_i^2\times(-1)^{dep_i+1} \]

同扫描线的思想,我们模拟出有一条垂直于\(x\)轴的直线从左向右移动。

  • 当其与某个圆开始触碰到的时候,就将这个圆拆分为上半圆和下半圆插入set中。这个set是按照圆的高度排序的。由于保证了圆和圆之间不相交,所以可以直接计算扫描线与半圆的交点作为关键字排序。圆和圆之间不相交保证了这个关键字的相对大小一定不会变。

查询刚刚插入的圆的上半圆的前驱(在set中的所有半圆中,在当前圆下方最近的半圆)。如果它的前驱是一个上半圆,那么当前圆的深度等于其前驱的深度;若前驱是一个下半圆,那么说明当前圆被其前驱所包含,深度为前驱的深度+1。

  • 遇到圆的右边界就直接将其两个半圆从set中删除即可。

因为set中要插入同一id的两个半圆,那么只要用up表示当前圆是上半圆还是下半圆即可。重载运算符时,可以比较当前扫描线与半圆的交点,也可以以圆心的纵坐标为第一关键字,以上/下半圆为第二关键字进行比较。

注意,如果是以扫描线与半圆的交点进行比较的话,在扫描线进入/离开一个圆的时候,扫描线与两个半圆的交点会重合。需要在比较函数中将上半圆的交点纵坐标加上一个\(eps\)即可。

Code

我的排序方法是比较当前半圆和扫描线的交点

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<limits.h>
#define IL inline
#define re register
#define LL long long
#define ULL unsigned long long
#ifdef TH
#define debug printf("Now is %d\n",__LINE__);
#else
#define debug
#endif
using namespace std;

template<class T>inline void read(T&x)
{
	char ch=getchar();
	int fu;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') fu=-1,ch=getchar();
	x=ch-'0';ch=getchar();
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	x*=fu;
}
inline LL read()
{
	LL x=0,fu=1;
	char ch=getchar();
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') fu=-1,ch=getchar();
	x=ch-'0';ch=getchar();
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*fu;
}
int G[55];
template<class T>inline void write(T x)
{
	int g=0;
	if(x<0) x=-x,putchar('-');
	do{G[++g]=x%10;x/=10;}while(x);
	for(int i=g;i>=1;--i)putchar('0'+G[i]);putchar('\n');
}
#define N 200010
int n,nowx;
LL X[N],Y[N],R[N];
struct Opt
{
	LL pos;
	int x;
	bool insert;
	Opt(LL a=0,int b=0,bool c=0)
	{
		pos=a,x=b,insert=c;
	}
	IL bool operator<(const Opt& z)const
	{
		return pos<z.pos;
	}
};
vector<Opt>opt;
bool dep[N];
#define eps 1e-6
struct O
{
	int x;
	bool up;
	O(int xx=0,bool u=1)
	{
		x=xx,up=u;
	}
	double calc()const
	{
		if(up) return Y[x]+sqrt(R[x]*R[x]-(X[x]-nowx)*(X[x]-nowx))+eps;
		return Y[x]-sqrt(R[x]*R[x]-(X[x]-nowx)*(X[x]-nowx));
	}
	IL bool operator<(const O& z)const
	{
		return calc()<z.calc();
	}
};
set<O>s;
set<O>::iterator it;
int main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		X[i]=read();
		Y[i]=read();
		R[i]=read();
		opt.push_back(Opt(X[i]-R[i],i,1));
		opt.push_back(Opt(X[i]+R[i],i,0));
	}
	sort(opt.begin(),opt.end());
	LL ans=0;
	for(int i=0;i<opt.size();i++)
	{
		nowx=opt[i].pos;
		if(opt[i].insert)
		{
			it=s.insert(O(opt[i].x,1)).first;
			if(it==s.begin())
			{
				dep[opt[i].x]=1;
			}
			else
			{
				it--;
				if(it->up)
				{
					dep[opt[i].x]=dep[it->x];
				}
				else
				{
					dep[opt[i].x]=dep[it->x]^1;
				}
			}
			s.insert(O(opt[i].x,0));
			if(dep[opt[i].x]) ans+=R[opt[i].x]*R[opt[i].x];
			else ans-=R[opt[i].x]*R[opt[i].x];
		}
		else
		{
			s.erase(O(opt[i].x,1));
			s.erase(O(opt[i].x,0));
		}
	}
	write(ans);
	return 0;
}

总结

扫描线是一种重要的思想。在其应用的过程中常常需要用到set​,线段树等数据结构进行维护。

posted @ 2021-03-01 11:08  Vanilla_chan  阅读(193)  评论(0编辑  收藏  举报