洛谷 P3268 [JLOI2016]圆的异或并 扫描线

之前做过一到类似的,当时没写题解,今天来补上。

首先我们发现圆没有交,所以两个圆只有包含和相离两种关系。

我们考虑用扫描线来处理,随着扫描线的推移,和上面的圆的交点 一直都在 和下面的圆的交点的上面,可以用 \(set\) 来维护相对位置

怎么确定一个圆应该是加还是减?我们将圆拆分成上半圆和下半圆,将上半圆插入 \(set\) 后,找在这扫描线上其下面的那个半圆。

\(1\) .若找不到,哪这个圆不会被包含,应该加上

\(2\) .若是下半圆,则和那个圆的加减状态相反。

\(3\) .若是上半圆,则和那个圆的加减状态相同。

上面的过程建议画一下图方便理解

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<set>
#define DB double
using namespace std;
int n, tot, now;
long long ans;
DB nowx;
const int N = 200010;
const DB eps = 1e-9;
int val[N];
DB x[N], y[N], r[N];
struct cir
{
	int opt, id;
	DB nowy()//求圆和当前扫描线交点的y坐标
	{
		return y[id] + (DB)opt * sqrt(r[id] * r[id] - (x[id] - nowx) * (x[id] - nowx) + eps);
	}
	friend bool operator <(cir x, cir y) {return x.nowy() < y.nowy();}
};
set<cir>s;
set<cir>::iterator it;
struct xian
{
	int opt, x, id;
	friend bool operator <(const xian &x, const xian &y) {return x.x < y.x;}
} li[N << 1];
int main()
{
	cin >> n;
	for (int i = 1; i <= n; ++i)
	{
		scanf("%lf%lf%lf", &x[i], &y[i], &r[i]);
		li[++tot] = (xian) {1, (int)(x[i] - r[i]), i};
		li[++tot] = (xian) {0, (int)(x[i] + r[i]), i};
	}
	sort(li + 1, li + 1 + tot);
	for (int i = 1; i <= tot; ++i)
	{
		nowx = li[i].x;
		if (li[i].opt)
		{
			now = li[i].id; it = s.insert((cir) {1, now}).first;
			if (it == s.begin())val[now] = 1;
			else
			{
				--it;
				if ((*it).opt == -1)val[now] = val[(*it).id] ^ 1;
				else val[now] = val[(*it).id];
			}
			s.insert((cir) { -1, now});
			if (val[now])ans += r[now] * r[now];
			else ans -= r[now] * r[now];
		}
		else s.erase((cir) {1, li[i].id}), s.erase((cir) { -1, li[i].id});
	}
	cout << ans;
	return 0;
}
posted @ 2020-04-08 11:09  wljss  阅读(205)  评论(0编辑  收藏  举报