bzoj 4561: [JLoi2016]圆的异或并(扫描线+set)

4561: [JLoi2016]圆的异或并

Time Limit: 30 Sec   Memory Limit: 256 MB
Submit: 715   Solved: 277
[ Submit][ Status][ Discuss]

Description

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

Input

 第一行包含一个正整数N,代表圆的个数。接下来N行,每行3个非负整数x,y,r,表示一个圆心在(x,y),半径为r的圆。保证|x|,|y|,≤10^8,r>0,N<=200000

Output

 仅一行一个整数,表示所有圆的异或面积并除以圆周率Pi的结果。

Sample Input

2
0 0 1
0 0 2

Sample Output

3


很显然如果该圆在偶数个圆里面,那么它的面积对答案贡献就是正的

如果该圆在奇数个圆里面,那么它的面积对答案贡献就是负的


将每个圆拆成两个点(x-r和x+r)

之后从左到右扫描,扫到一个圆就看它上面最近的一个圆是正贡献还是负贡献,具体看下图


其中当遍历到图中第二小的那个圆的左端时

很显然它上面最靠近它的圆面积是正的,

但是你首先遇到的会是B点,而B点在大圆的下方,所以可知当前小圆面积也是正的

计算完之后将该圆插入

……

同理遍历到图中最小的圆时,

虽然它最上面的最靠近它的圆面积也是正的,但竖线交点那个点在大圆的上方,所以面积为负

……

当遍历到图中第二小的那个圆的右端时,说明整个圆已经没有意义了,删除该圆,其它同理

可以用set维护,用set查询上面最靠近的圆

#include<stdio.h>
#include<set>
#include<math.h>
#include<algorithm>
using namespace std;
#define LL long long
typedef struct Res
{
	int x, y, r;
	int t, val, id;
}Circle;
Circle s[600005], c[300005], temp;
typedef struct Pset
{
	int id, t, val;
	bool operator < (const Pset &b) const
	{
		if(id==0)
			return 1;
		else if(b.id==0)
			return 0;
		else if(id==b.id)
		{
			if(t>b.t)
				return 1;
			return 0;
		}
		else
		{
			if(c[id].y+t*sqrt((LL)c[id].r*c[id].r-(LL)(temp.x-c[id].x)*(temp.x-c[id].x))>c[b.id].y+b.t*sqrt((LL)c[b.id].r*c[b.id].r-(LL)(temp.x-c[b.id].x)*(temp.x-c[b.id].x)))
				return 1;
			return 0;
		}
	}
}Pset;
Pset now;
set<Pset> st;
bool comp(Circle a, Circle b)
{
	if(a.x<b.x || a.x==b.x && a.t<b.t || a.x==b.x && a.t==b.t && a.t*a.r>b.t*b.r)
		return 1;
	return 0;
}
int main(void)
{
	LL ans = 0;
	set<Pset>::iterator it;
	int cnt, n, i;
	scanf("%d", &n);
	cnt = 0;
	for(i=1;i<=n;i++)
	{
		scanf("%d%d%d", &c[i].x, &c[i].y, &c[i].r);
		s[++cnt].x = c[i].x-c[i].r, s[cnt].y = c[i].y, s[cnt].t = 1, s[cnt].r = c[i].r, s[cnt].id = i;
		s[++cnt].x = c[i].x+c[i].r, s[cnt].y = c[i].y, s[cnt].t = -1, s[cnt].r = c[i].r, s[cnt].id = i;
	}
	n = cnt;
	sort(s+1, s+n+1, comp);
	now.id = 0, now.val = -1, now.t = 1;
	st.insert(now);
	for(i=1;i<=n;i++)
	{
		if(s[i].t==1)
		{
			temp = s[i];
			now.id = s[i].id;
			now.t = 1;
			it = --st.upper_bound(now);
			if((*it).t==1)
				now.val = -(*it).val;
			else
				now.val = (*it).val;
			s[i].val = now.val;
			ans += (LL)now.val*temp.r*temp.r;
			st.insert(now);
			now.t = -1;
			st.insert(now);
		}
		else
		{
			now.id = s[i].id;
			now.t = 1;
			st.erase(now);
			now.t = -1;
			st.erase(now);
		}
	}
	printf("%lld\n", ans);
	return 0;
}
/*
3
0 0 4
0 2 2
0 -2 2
*/


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值