扫描线
引入
先来看一道题:油漆面积。
给出若干个矩形,求这些矩形的面积并。
这题数据范围很小,用一个二维差分就行了。
那把范围调大呢?(即模板扫描线)
这个时候就需要使用线段树来维护扫描线了。
思想
想象一下,在平面直角坐标系上面有很多个矩形,有一条线从底部开始往顶部进行扫描,每次统计在 y 坐标的一个区间内且每行被至少一个矩形覆盖的部分完全相同的部分对于答案的贡献。
\[\texttt{图片来源 OI-wiki}
\]
这个要怎么维护呢?尝试把矩形变成两条线段去做贡献,当扫描线扫到一个矩形的下底时,会使得这个矩形对应的 x 坐标区间内每个元素都加一,同理扫到上底的时候每个元素都要减一。这就变成了区间修改。
那么算贡献呢?只要知道有多少元素为正数,用线段树去维护即可。
由于模板数据范围较大,需要离散化,注意处理边界的细节。
Code
用的永久化标记的写法。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 10;
struct Edge {
int h, x, y, z;
} e[N];
struct SegTree {
int l, r, cov, len;
} tr[N * 8];
int n, dce[N], dce_[N], dcnt, ally[N];
ll ans;
void pushup (int id) {
// tr[id].cov = min(tr[id * 2].cov, tr[id * 2 + 1].cov);
if (tr[id].cov)
tr[id].len = tr[id].r - tr[id].l;
else
tr[id].len = tr[id * 2].len + tr[id * 2 + 1].len;
}
void build (int id, int l, int r) {
tr[id] = {dce_[l], dce_[r + 1]};
if (l == r)
return ;
int mid = (l + r) >> 1;
build(id * 2, l, mid), build(id * 2 + 1, mid + 1, r);
}
void modify (int id, int l, int r, int x, int y, int z) {
if (x <= tr[id].l && tr[id].r <= y) {
tr[id].cov += z, pushup(id);
return ;
}
if (y < tr[id].l || tr[id].r < x)
return ;
int mid = (l + r) >> 1;
modify(id * 2, l, mid, x, y, z), modify(id * 2 + 1, mid + 1, r, x, y, z);
pushup(id);
}
int main () {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for (int i = 1, x1, y1, x2, y2; i <= n; i++) {
cin >> x1 >> y1 >> x2 >> y2, dce[i * 2 - 1] = x1, dce[i * 2] = x2;
e[i * 2 - 1] = {y1, x1, x2, 1}, e[i * 2] = {y2, x1, x2, -1};
ally[i * 2 - 1] = y1, ally[i * 2] = y2;
}
n *= 2, sort(dce + 1, dce + n + 1), sort(e + 1, e + n + 1, [](const Edge &i, const Edge &j){return i.h < j.h;}), sort(ally + 1, ally + n + 1), dce[0] = -1;
for (int i = 1; i <= n; i++)
if (dce[i] != dce[i - 1])
dce_[++dcnt] = dce[i];
build(1, 1, dcnt - 1);
for (int i = 1, j = 1; i < n; i++) {
for (; j <= n && e[j].h == ally[i]; j++)
modify(1, 1, dcnt - 1, e[j].x, e[j].y, e[j].z);
ans += 1ll * (ally[i + 1] - ally[i]) * tr[1].len;
}
cout << ans;
return 0;
}