【模板】扫描线
题目描述
求 $n$ 个四边平行于坐标轴的矩形的面积并。
输入格式
第一行一个正整数 $n$。
接下来 $n$ 行每行四个非负整数 $x_1, y_1, x_2, y_2$,表示一个矩形的四个端点坐标为 $(x_1, y_1),(x_1, y_2),(x_2, y_2),(x_2, y_1)$。
输出格式
一行一个正整数,表示 $n$ 个矩形的并集覆盖的总面积。
样例 #1
样例输入 #1
2
100 100 200 200
150 150 250 255
样例输出 #1
18000
提示
对于 $20\%$ 的数据,$1 \le n \le 1000$。
对于 $100\%$ 的数据,$1 \le n \le {10}^5$,$0 \le x_1 < x_2 \le {10}^9$,$0 \le y_1 < y_2 \le {10}^9$。
解题思路
扫描线经典问题,需要用线段树来进行优化。记录这篇题解主要是介绍常规的懒标记线段树写法,大部分题解给出的都是不下传懒标记的做法,然而一年多过去了我还是理解不了这样做为什么是正确的,所以总是忘掉然后回来复习结果还是看不懂。然后昨天又碰到了扫描线相关的题目,复习的时候看到了这篇题解,给出线段树的另外一种写法,现在是终于知道怎么用线段树求矩形并集的面积了。
还是先介绍一下求矩形并集的面积的问题,就是二维平面上有若干个四边平行于坐标轴的矩形,他们之间可能会有重叠,问这些矩形的并集的面积是多少。
看上去好像是容斥原理的问题,不过这样做的话会相当麻烦。扫描线的思想就是用平行于竖轴的水平线,根据矩形的竖边将整个不规则图形划分为若干个区域,如下图:
通过观察可以知道,两条竖线 $x_i$ 和 $x_{i+1}$ 之间有若干个规则的矩形,这些矩形的长都是 $x_{i+1} - x_i$,宽的总和记作 $y_i$。要计算 $n$ 个矩形并集的面积,等价于分别计算 $2n -1$ 条竖线间的矩形的面积再求和,即 $S = \sum\limits_{i=1}^{2n-1}{y_i \cdot (x_{i+1} - x_i)}$。
问题是如何得到每个 $y_i$?我们用一个数组来记录竖轴上每个位置(线段)被覆盖的次数(当然由于题目中 $y_i$ 的值域很大,需要离散化),那么有被覆盖的位置的总长度就是 $y_i$ 了。
假设矩形的位置用左下角 $(x_1, y_1)$ 和右上角 $(x_2, y_2)$ 来表示,那么用四元组 $(x_1, y_1, y_2, 1)$ 和 $(x_2, y_1, y_2, -1)$ 来表示矩形的两条竖边。以第一个参数 $x$ 为关键字对四元组从小到大排序,那么就会得到上图中从左到右的 $2n$ 条竖线。依次遍历每条竖线,如果第四个参数是 $1$ 则在数组中把 $y_1 \sim y_2$ 的位置覆盖一次,否则是 $-1$ 则把 $y_1 \sim y_2$ 的位置删除一次覆盖。这样就可以维护出每个 $y_i$,不过在考虑对纵坐标离散化后这种暴力做法的时间复杂度是 $O(n^2)$。
由于涉及到区间加的问题,因此可以考虑用线段树来维护。这里就不给出大部分题解用到的做法了,下面介绍用常规懒标记线段树的写法,并不复杂而且更容易理解。
在这个问题中,线段树本质上是维护至少被覆盖一次的线段的数量(总长度),修改的操作只有区间加,并且保证任意修改后不会出现负数。因此线段树节点只需维护如下的信息:
struct Node {
int l, r, mn, s, add;
};
其中 $\text{mn}$ 是 $[l,r]$ 这个区间中所有线段被覆盖的次数的最小值。$s$ 是被覆盖次数为 $\text{mn}$ 的所有线段的长度。$\text{add}$ 是区间加的懒标记。
对于某个区间中的线段,
- 如果 $\text{mn} > 0$,意味着该区间中所有的线段都被覆盖,那么被覆盖的线段总长度就是区间的长度。
- 否则 $\text{mn} = 0$,意味着存在某些线段没有被覆盖,那么被覆盖的线段的总长度就是区间的长度减去 $s$。
在修改(维护的区间被修改的区间完全覆盖)和懒标记下传的操作中,如果区间加的值是 $c$,只需让 $\text{mn} \gets \text{mn} + c$,$\text{add} \gets \text{add} + c$,而不用改变 $s$,这是因为对区间的所有线段都覆盖一次后,覆盖次数最小的线段还是之前的那些。
AC 代码如下,时间复杂度为 $O(n \log{n})$:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
struct Seg {
int x, y1, y2, c;
}p[N];
int xs[N], sz;
struct Node {
int l, r, mn, s, add;
}tr[N * 4];
void build(int u, int l, int r) {
tr[u] = {l, r, 0, 0, 0};
if (l == r) {
tr[u].s = xs[r + 1] - xs[l];
}
else {
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
tr[u].s = tr[u << 1].s + tr[u << 1 | 1].s;
}
}
void pushdown(int u) {
if (tr[u].add) {
tr[u << 1].mn += tr[u].add;
tr[u << 1].add += tr[u].add;
tr[u << 1 | 1].mn += tr[u].add;
tr[u << 1 | 1].add += tr[u].add;
tr[u].add = 0;
}
}
void modify(int u, int l, int r, int c) {
if (tr[u].l >= l && tr[u].r <= r) {
tr[u].mn += c;
tr[u].add += c;
}
else {
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, c);
if (r >= mid + 1) modify(u << 1 | 1, l, r, c);
tr[u].mn = min(tr[u << 1].mn, tr[u << 1 | 1].mn);
tr[u].s = 0;
if (tr[u << 1].mn == tr[u].mn) tr[u].s += tr[u << 1].s;
if (tr[u << 1 | 1].mn == tr[u].mn) tr[u].s += tr[u << 1 | 1].s;
}
}
int find(int x) {
int l = 1, r = sz;
while (l < r) {
int mid = l + r >> 1;
if (xs[mid] >= x) r = mid;
else l = mid + 1;
}
return l;
}
int main() {
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
int x1, y1, x2, y2;
scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
p[i << 1] = {x1, y1, y2, 1};
p[i << 1 | 1] = {x2, y1, y2, -1};
xs[++sz] = y1, xs[++sz] = y2;
}
sort(xs + 1, xs + sz + 1);
sz = unique(xs + 1, xs + sz + 1) - xs - 1;
sort(p, p + 2 * n, [&](Seg &a, Seg &b) {
return a.x < b.x;
});
build(1, 1, sz - 1);
LL ret = 0;
for (int i = 0; i < 2 * n - 1; i++) {
modify(1, find(p[i].y1), find(p[i].y2) - 1, p[i].c);
LL len = xs[sz] - xs[1] - !tr[1].mn * tr[1].s;
ret += len * (p[i + 1].x - p[i].x);
}
printf("%lld", ret);
return 0;
}
参考资料
题解 P5490 【【模板】扫描线】:https://www.luogu.com.cn/blog/xuxuxuxuxu/solution-p5490
AcWing 247. 亚特兰蒂斯(算法提高课):https://www.acwing.com/video/653/
标签:le,覆盖,int,text,线段,扫描线,矩形,模板 From: https://www.cnblogs.com/onlyblues/p/17836031.html