[洛谷] P3268 圆的异或并(扫描线)
传送门: 圆的异或并
思路:
设最外层的圆的层级为 1 级,根据容斥关系不难得出最终所求的面积为:奇数层的圆面积之和 - 偶数层的面积之和,所以关键是如何求每个圆的层级。
假设我们在图上任意画一条平行于 x 轴的直线,与图上部分圆相交,这些交点一定是成对出现的,而且由于题目中的圆一定不相交,这些成对的点之间一定是满足括号嵌套关系的,我们将所有的交点按 x 排序:
- 如果某圆 A 的左交点的左侧是某圆 B 的右交点,那么 A 与 B 同层级(如果 B 的交点存在,则其层级一定已经求出)
- 如果某圆 A 的左交点的左侧是某圆 B 的左交点,那么 A 的层级比 B 多一层
- 特殊情况,A 的左交点的左侧没有其它交点,说明 A 在最外层,层级为 1
于是,我们可以先将每个圆的上下顶点保存并按 y 排序,再用一条平行于 x 轴的扫描线从下往上扫描:
- 如果遇到圆的下顶点,则将圆的左右交点加入 set 中,set 按 x 排序
- 如果遇到圆的上顶点,则将圆的左右交点从 set 中删去,即删除该圆
交点的坐标不能直接存圆的左右顶点,这样层级会出错,需要用当前扫描线位置实时计算。由于圆不会相交,所以已存入的交点间的关系是不会改变的,实时计算的交点坐标只会影响新插入的点的位置。
还有一个需要注意的点是插入时左右交点的坐标是相同的,如果不做处理第二个交点就无法插入 set 中,这一点很好处理,将左交点向左挪 eps,右交点向右挪 eps 即可
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef tuple<ll, ll, ll> tup;
#define fsio ios::sync_with_stdio(false)
const int inf = 0x3f3f3f3f;
const int maxn = 5e5+10;
const double eps = 1e-8;
int nowy, sg[maxn];
tup s[maxn];
struct node
{
ll y, id;
bool ud;
bool operator < (const node &b) const
{
return y < b.y;
}
}p[maxn];
struct node2
{
ll id;
bool lr;
double nowx() const
{
auto [x, y, r] = s[id];
if (lr) return (double)x - (double)sqrt(r*r - (y-nowy)*(y-nowy)) - eps;
else return (double)x + (double)sqrt(r*r - (y-nowy)*(y-nowy)) + eps;
}
bool operator < (const node2 &b) const
{
return nowx() < b.nowx();
}
};
int main()
{
int n;
while(cin>>n)
{
for (int i = 0; i < n; i++)
{
ll x, y, r;
cin>>x>>y>>r;
s[i] = {x, y, r};
p[i] = {y-r, i, 0};
p[n+i] = {y+r, i, 1};
}
sort(p, p+2*n);
set<node2> st;
ll ans = 0;
for (int i = 0; i < 2*n; i++)
{
ll y = p[i].y, id = p[i].id, r = get<2>(s[id]);
bool ud = p[i].ud;
nowy = y;
if (ud)
{
st.erase({id, 1});
st.erase({id, 0});
}
else
{
st.insert({id, 0});
set<node2>::iterator it = st.insert({id, 1}).first;
if (it == st.begin())
{
sg[id] = 1;
}
else
{
it--;
if (it->lr) sg[id] = -sg[it->id];
else sg[id] = sg[it->id];
}
ans += sg[id]*r*r;
}
}
cout<<ans<<endl;
}
return 0;
}