扫描线
在扫描线中,对于区间的表示有些不同,因此 \(build\) 的过程以及表示都会有些不同,这里【link】 有两种扫描线的线段树的构建方式,可以作为参考
扫描线是对线段树的一种应用,做法是逐个扫描每个矩形的上下边,并用线段树维护图形的宽,以此得到整个图形覆盖的面积
1. 需要存储的东西
每个矩形的上下边,去重过的横坐标,带标记(区间覆盖次数)的线段树
const int N = 2E5 + 10;
struct L {
int x1, x2, y, k;
bool operator<(const L &t) const{return y < t.y; }
}line[N];
struct Node {
int l, r, cnt, len;
}tr[N << 3];
int n, X[N];
2. 预处理
将 \(x\) 坐标离散化,记录覆盖与取消覆盖的边
cin >> n;
for (int i = 1; i <= n; i ++) {
int a, b, c, d;
cin >> a >> b >> c >> d;
X[i] = a, X[i + n] = c;
line[i] = {a, c, b, 1};
line[i + n] = {a, c, d, -1};
}
n <<= 1;
sort(X + 1, X + n + 1);
sort(line + 1, line + n + 1);
int s = unique(X + 1, X + n + 1) - X - 1;
3. 建立连续的线段树
文首有两种建树方式,第二种比较形象,但不如第一种方便,我们用第一种,即“右端点偏移”的方式建树
如果用传统的线段树建立方法,\(Node.l\) 和 \(Node.r\) 表示线段的左右端点
build(ls, l, mid);
build(rs, mid + 1, r);
中间会有一个空隙,\(mid\) 到 \(mid + 1\) 这两个点中间有一个线段是无法被包含的,并且\(tr[p].l == tr[p].r\) 时也无实际意义
那么,我们可以将右端点偏移,\(tr[p].r\) 来表示右端点为 \(X[tr[p].r + 1]\) 这个点,我们发现,这样可以表示每个线段了
这种偏移对于之后的计算有什么影响?
当修改 \(l\) 到 \(r\) 的线段时,对应到线段树中是 \(r - 1\), \(update(1, l, r - 1, k)\);
建立线段树时,\(tr[p].r\) 最大为 \(s - 1\), 所以 \(build(1, 1, s - 1);\)
4. \(pushup\) 的写法
在扫描线中,只要线段被覆盖,即 \(tr[p].cnt != 0\),此时覆盖的长度就是这个线段的总长
否则,线段被覆盖的长度是两个儿子节点被覆盖长度的和
void upshup(int p) {
if (tr[p].cnt) tr[p].len = X[tr[p].r + 1] - X[tr[p].l];
else tr[p].len = tr[ls].len + tr[rs].len;
}
5. 区间修改
对于区间进行修改时,由于有可能改变区间覆盖的次数,会在更新线段后 \(pushup\) 一下,更新当前线段覆盖长度
这里也是寻常线段树开 \(4\) 倍空间,而扫描线需要开 \(8\) 倍的原因,可能会在叶子节点\((tr[p].l == tr[p].r)\) 再往后考虑它的儿子节点,可以发现,这是超出线段树 \(4\) 倍空间的操作
void update(int p, int l, int r, int k) {
if (l <= tr[p].l && tr[p].r <= r) {
tr[p].cnt += k;
pushup(p);
return;
}
int mid = tr[p].l + tr[p].r >> 1;
if (l <= mid) update(ls, l, r, k);
if (r > mid) update(rs, l, r, k);
pushup(p);
}
6. 计算面积
接下来就要计算面积了,面积的计算方法是由下至上遍历扫描线,利用二分找到离散化后的线段,更新后用这个长度乘高(与下一条扫描线的高度差)
直接上代码吧
// 1 or 2 都可以
long long ans = 0;
// 1
for (int i = 1; i <= n; i ++) {
ans += 1LL * tr[1].len * (line[i].y - line[i - 1].y);
int l = lower_bound(X + 1, X + s + 1, line[i].x1) - X;
int r = lower_bound(X + 1, X + s + 1, line[i].x2) - X;
update(1, l, r - 1, line[i].k);
}
// 2
for (int i = 1; i < n; i ++) {
int l = lower_bound(X + 1, X + s + 1, line[i].x1) - X;
int r = lower_bound(X + 1, X + s + 1, line[i].x2) - X;
update(1, l, r - 1, line[i].k);
ans += 1LL * tr[1].len * (line[i].y - line[i - 1].y);
}
完整代码
#include <bits/stdc++.h>
#define ls (p << 1)
#define rs (p << 1 | 1)
using namespace std;
const int N = 2e5 + 10;
typedef long long ll;
struct L {
int x1, x2, y, k;
bool operator<(const L &t)const {return y < t.y; }
}line[N];
struct Node {
int l, r, cnt, len;
}tr[N << 3];
int X[N], n;
void build(int p, int l, int r) {
tr[p] = {l, r, 0, 0};
if (l == r) return;
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
}
void pushup(int p) {
if (tr[p].cnt) tr[p].len = X[tr[p].r + 1] - X[tr[p].l];
else tr[p].len = tr[ls].len + tr[rs].len;
}
void update(int p, int l, int r, int k) {
if (l <= tr[p].l && tr[p].r <= r) {
tr[p].cnt += k;
pushup(p);
return;
}
int mid = tr[p].l + tr[p].r >> 1;
if (l <= mid) update(ls, l, r, k);
if (r > mid) update(rs, l, r, k);
pushup(p);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i ++) {
int a, b, c, d;
cin >> a >> b >> c >> d;
line[i] = {a, c, b, 1};
line[i + n] = {a, c, d, -1};
X[i] = a, X[i + n] = c;
}
n <<= 1;
sort(X + 1, X + n + 1);
sort(line + 1, line + n + 1);
int s = unique(X + 1, X + n + 1) - X - 1;
build(1, 1, s - 1);
ll ans = 0;
for (int i = 1; i <= n; i ++) {
ans += 1LL * (line[i].y - line[i - 1].y) * tr[1].len;
int l = lower_bound(X + 1, X + s + 1, line[i].x1) - X;
int r = lower_bound(X + 1, X + s + 1, line[i].x2) - X;
update(1, l, r - 1, line[i].k);
}
cout << ans << endl;
return 0;
}