扫描线+离散化
本文主要想复习一下线段树扫描线,主要参考本文
扫描线
扫描线主要用于求多个矩形进行叠加产生的面积,如下图中的两个矩形,求其总面积。这时候只有两个矩形,看起来好像还很简单,如果暴力的话可以求出交点将两个矩形面积相加减去叠加面积即可。但是如果矩形数量增多那么这样的方法必然是不可取的。
那应该怎么求呢。有的人就提出了使用一条线来扫描整个图像,这样我们就可以通过扫描线来将图像分割成若干个矩形。这时,当扫描线遇到矩形的边时我们只需要计算当前图形与线的相交的宽度和与下次遇到的矩形的边之间的高度即可得到当前扫描的矩形。
因此我们应该定义一个结构来存储边的信息。这里还需要注意的是,这条边是要进入矩形还是要出矩形。所以我们可以得到如下定义:
这里我们重载了小于运算符,主要是为了后面的排序。因为我们的扫描线是从下往上扫描,所以我们按照线的高度从低到高排序,然后遍历一次所有的线就模拟出了扫描线从下往上扫描的行为。
struct Line {
int l, r; // left and right endpoints
int h; // y-coordinate of this line
int type; // upper or lower bound of the rectangle
/**
* @brief This operator override let the Scanner scans upward.
*
* @param obj - a comparable Line instance
* @return bool - if the order is right
*/
bool operator< (const Line& obj) const {
return h < obj.h;
}
};
此外,我们还要将所有的横坐标记录下来,具体为什么,请继续看下面。具体横坐标的定义,我们就定义为x[]
数组吧。
这里我们已经有了线段和横坐标,我们应该怎么知道当前矩形的宽度还不记入重叠部分呢。这就需要引出我们的线段树了,这也是为什么我们要记录坐标的原因。
如上图,我们用线段树记录当前的矩形图形总宽度,即使用线段树记录矩形覆盖的宽度。如我们从下往上遍历边:
- 我们一开始拿到最下方的线段,发现是进入矩形,这时我们更新线段树来覆盖
[x[1], x[3]]
区间; - 当我们得到第二条线段时,发现是进入矩形,我们更新线段树来覆盖
[x[2], x[4]]
区间,这时我们记录[x[2], x[3]]
被完全覆盖了两次,但是我们计算被覆盖长度时只关心这一段是否被覆盖,所以总共覆盖的区间为[x[1], x[4]]
,这样就不会出现重复; - 得到第三条边时,发现是出矩形,那我们就更新线段树来去除对该区间的覆盖,但是
[x[2], x[3]]
区间被覆盖了两次,所以我们这样操作只会去除掉蓝色矩形对区间覆盖的影响,但是不会影响黄色矩形的覆盖宽度。因此,这时的[x[2], x[3]]
覆盖次数只剩了一次,也就是黄色矩形覆盖导致的。这时线段树更新后覆盖的总区间为[x[2], x[4]]
。 - 我们并不会遍历最上方的边,因为我们需要求面积,4条边将把图形分为3部分,所以遍历下方的三条边即可计算出所有面积。
当然,为了减少重复造成的性能损失,我们可以对 x 坐标进行离散化以提高线段树的性能。
如果还是没有讲清楚,可以结合代码来进一步理解。
/**
* @file SegTreeScanner.cpp
* @author Xu.C (steve_curcy@163.com)
* @brief This code is a template of Segment-Tree-Scanner, leaves of the tree is points not lines.
* @version 0.1.1
* @date 2022-11-14
*
* @copyright Copyright (c) 2022 Xu.Cao
*
* <p>Linsences: GPL version2.0</p>
*
*/
#include <bits/stdc++.h>
using namespace std;
// the max number of rectangles
constexpr int MAXN = 1e4 + 5;
// the line of Scanner
struct Line {
int l, r; // left and right endpoints
int h; // y-coordinate of this line
int type; // upper or lower bound of the rectangle
/**
* @brief This operator override let the Scanner scans upward.
*
* @param obj - a comparable Line instance
* @return bool - if the order is right
*/
bool operator< (const Line& obj) const {
return h < obj.h;
}
} lines[MAXN << 1]; // two times of the numebr of rectangles
// node of segment tree
struct Node {
int len; // the length of covered line in this part
int cover; // if this part of line is covered exactly
} tree[MAXN << 4];
int x[MAXN << 1];
/**
* @brief build a segment tree
*
* @param l - left endpoint of current node's range
* @param r - right endpoint of current node's range
* @param cur - current node's Id
*/
void build(int l, int r, int cur) {
tree[cur].cover = 0;
tree[cur].len = 0;
if (l == r) return ;
int mid = (l + r) >> 1;
build(l, mid, cur << 1);
build(mid + 1, r, (cur << 1) | 1);
}
/**
* @brief To push the status from children to parent
*
* @param l - left endpoint of current node's range
* @param r - right endpoint of current node's range
* @param cur - current node's Id
*/
void pushUp(int l, int r, int cur) {
if (tree[cur].cover) {
// length is whole line if covered exactly
tree[cur].len = x[r + 1] - x[l];
} else {
tree[cur].len = tree[cur << 1].len + tree[(cur << 1) | 1].len;
}
}
/**
* @brief update interval's value, which means entering or going out a rectangle
*
* @param L - left endpoint of target interval
* @param R - right endpoint of target interval
* @param l - left endpoint of current node's range
* @param r - right endpoint of current node's range
* @param cur - current node's Id
* @param val - the value to update, which means the type of a line.
*/
void update(int L, int R, int l, int r, int cur, int val) {
if (x[r + 1] <= L || R <= x[l]) return;
if (L <= x[l] && x[r + 1] <= R) {
// covered exactly
tree[cur].cover += val;
pushUp(l, r, cur);
return ;
}
int mid = (l + r) >> 1;
update(L, R, l, mid, cur << 1, val);
update(L, R, mid + 1, r, (cur << 1) | 1, val);
pushUp(l, r, cur);
}
int main() {
int n, x1, x2, y1, y2;
int ans = 0;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
x[(i << 1) - 1] = x1, x[i << 1] = x2;
lines[(i << 1) - 1] = (Line){x1, x2, y1, 1};
lines[i << 1] = (Line){x1, x2, y2, -1};
}
n <<= 1;
sort(lines + 1, lines + n + 1);
sort(x + 1, x + n + 1);
int cnt = unique(x + 1, x + n + 1) - x - 1;
for (int i = 1; i <= cnt; i++) printf("%d ", x[i]);
cout << endl;
build(1, cnt - 1, 1);
for (int i = 1; i < n; i++) {
update(lines[i].l, lines[i].r, 1, cnt - 1, 1, lines[i].type);
ans += tree[1].len * (lines[i + 1].h - lines[i].h);
}
printf("%d\n", ans);
return 0;
}