题解
使用线段树+扫描线求解
将矩形分割为上下两条边 记录边的左端点和右端点分别对应矩形的左右侧边 上下边的高度分别对应矩形的上下边 和符号 上为正下为负
将边按照高度从高到低排序处理 每次处理过程中根据符号在线段树中离散化标记覆盖范围并计算覆盖长度 长度分为覆盖一次和两次的长度分别计算
每次扫描用覆盖两次的长度乘上距离下条边的高度差为当前分块的答案贡献 求和即可
AC代码
#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 2e3 + 10; //*2
vector<double> dz; //离散化类型为double
struct node2
{
double l, r, h; //横向边
int f; //上下边
bool operator < (const node2 &oth) const
{
return h < oth.h;
}
}a[MAXN];
struct node
{
int l, r, cnt; //叶节点覆盖次数
double s1, s2; //覆盖一次两次的长度
}tre[MAXN * 4];
inline void PushUp(int x)
{
if (tre[x].cnt > 0) //只要被覆盖则s1等于区间长
tre[x].s1 = dz[tre[x].r + 1] - dz[tre[x].l];
else if (tre[x].l == tre[x].r) //一个点为0
tre[x].s1 = 0;
else //不是一个点则合并儿子
tre[x].s1 = tre[x << 1].s1 + tre[x << 1 | 1].s1;
if (tre[x].cnt > 1) //两次覆盖则s2等于区间长
tre[x].s2 = dz[tre[x].r + 1] - dz[tre[x].l];
else if (tre[x].l == tre[x].r)
tre[x].s2 = 0;
else if (tre[x].cnt == 1) //如果覆盖了一次 s2则为儿子s1的长度和
tre[x].s2 = tre[x << 1].s1 + tre[x << 1 | 1].s1;
else //如果一次都没 考虑儿子的s2
tre[x].s2 = tre[x << 1].s2 + tre[x << 1 | 1].s2;
}
void Build(int x, int l, int r)
{
tre[x].l = l, tre[x].r = r, tre[x].cnt = tre[x].s1 = tre[x].s2 = 0;
if (l == r)
return;
int m = l + r >> 1;
Build(x << 1, l, m);
Build(x << 1 | 1, m + 1, r);
}
void Update(int x, int pl, int pr, int v) //每次只查询根节点 不需要lzay和Query
{
int l = tre[x].l, r = tre[x].r;
if (pl <= l && r <= pr)
{
tre[x].cnt += v; //加一或者减一
PushUp(x); //修改完毕直接更新
}
else
{
int m = l + r >> 1;
if (pl <= m)
Update(x << 1, pl, pr, v);
if (pr > m)
Update(x << 1 | 1, pl, pr, v);
PushUp(x);
}
}
int Dis(double v)
{
return lower_bound(dz.begin(), dz.end(), v) - dz.begin();
}
int main()
{
#ifdef LOCAL
freopen("C:/input.txt", "r", stdin);
#endif
int T;
cin >> T;
while (T--)
{
dz.clear();
dz.push_back(-INF);
int N;
cin >> N;
for (int i = 1; i <= N; i++) //将矩形拆分为一正一负两条横向边
{
double x1, y1, x2, y2;
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
a[i * 2 - 1] = { y1, y2, x1, 1};
a[i * 2] = { y1, y2, x2, -1};
dz.push_back(y1), dz.push_back(y2); //离散化纵坐标
}
N *= 2;
sort(a + 1, a + N + 1); //按照高度从上到下排序
sort(dz.begin(), dz.end());
dz.erase(unique(dz.begin(), dz.end()), dz.end());
double ans = 0.0;
Build(1, 1, dz.size());
for (int i = 1; i < N; i++) //从上向下扫描 不扫描最后一个
{
Update(1, Dis(a[i].l), Dis(a[i].r) - 1, a[i].f); //更新[l, r)范围
ans += (a[i + 1].h - a[i].h) * tre[1].s2; //到下一个线段的高度差*整个纵坐标范围的覆盖面积
}
printf("%.2f\n", ans);
}
return 0;
}