题解
学习博客↓
题解 P5490 【【模板】扫描线】
【学习笔记】扫描线
扫描线建议纸上模拟一遍,这样才更加清晰
记几个重点:
假设从下往上扫描,
每条边按照 y y y 值坐标从小到大排序,标记其是矩形的上底(标记为+1),还是下底(标记为-1)
这样,对于每个矩形,扫描线总是会先碰到下边,然后再碰到上边
建立一棵线段树,其每个端点维护一条线段(也就是一个区间)的信息:
1.该线段被覆盖了多少次(被多少个矩形所覆盖)- sum
2.该线段内被整个图形所截的长度是多少 - len
不过不同于以往的线段树,这里的线段树里每个节点表示的是一条线段,长度
>
0
>0
>0 ,区间
[
l
,
r
]
[l,r]
[l,r]表示的是
X
l
∼
r
+
1
X_{l\sim r+1}
Xl∼r+1,用图表示就是长这样子
代码
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int n;
int X[N << 1];
ll x1, x2, y1, y2; // 有y1 所以避开了bits头
namespace ScanningLine {//扫描线板子
#define lson (rt<<1)
#define rson (rt<<1|1)
struct ScanLine { //扫描线
ll l, r, h; // 左右端点 y轴坐标
int mark; // 用于保存权值1/-1 从下往上扫描 下底+1 上底-1
bool operator<(const ScanLine &b) const {
return h < b.h; //根据y轴坐标从小到大排序
}
} line[N << 1];
//扫描线从矩形的下底扫到矩形的上底
struct SegTree {
int l, r;
int sum; //统计的是
ll len;
} tree[N << 2];
void pushup(int rt) {
int l = tree[rt].l, r = tree[rt].r;
if (tree[rt].sum) { //不为0 说明该区间被线段覆盖了
tree[rt].len = X[r + 1] - X[l];//更新长度
} else {
tree[rt].len = tree[lson].len + tree[rson].len; //合并儿子信息
}
}
void build(int l, int r, int rt) {
//区间[l,r] 表示线段[l,r+1] <- 没写错
//因为线段树每个节点表示的是一个线段 要有长度 像[x,x]就不可以表示成一个点
tree[rt] = {l, r, 0, 0};
if (l == r) {
return;
}
int mid = l + r >> 1;
build(l, mid, lson);
build(mid + 1, r, rson);
}
void update(int L, int R, int c, int rt) {
int l = tree[rt].l, r = tree[rt].r;
if (X[r + 1] <= L || R <= X[l]) return;
//因为线段树节点表示的特殊性 需要额外的判断 当前区间是否是我们需要的那个
if (L <= X[l] && X[r + 1] <= R) {//能到这一步说明有 一部分 线段是在该区间内的
tree[rt].sum += c; //对线段打标记
pushup(rt);
return;
}
update(L, R, c, lson);
update(L, R, c, rson);
pushup(rt);
}
}
using namespace ScanningLine;
int main() {
ios::sync_with_stdio(0);
cin >> n;//n个矩形
for (int i = 1; i <= n; ++i) {
cin >> x1 >> y1 >> x2 >> y2; // 左下角 右上角
X[2 * i - 1] = x1;
X[2 * i] = x2;
line[2 * i - 1] = {x1, x2, y1, 1};
line[2 * i] = {x1, x2, y2, -1};
//一条线段含两个端点 一个矩形的上下边都需要扫描线扫过
}
n <<= 1;
sort(line + 1, line + n + 1);
sort(X + 1, X + n + 1);
int tot = unique(X + 1, X + n + 1) - X - 1;
build(1, tot - 1, 1);//节点[1,tot-1]表示的线段是[1,tot]
ll ans = 0;
for (int i = 1; i < n; ++i) {
update(line[i].l, line[i].r, line[i].mark, 1); // 线段的起点 线段的终点 上底/下底 根节点
ans += tree[1].len * (line[i + 1].h - line[i].h); //tree[1].len表示扫描线扫到的线段的长度
}
cout << ans << endl;
return 0;
}