扫描线
文章目录
1.算法分析
1.1 基本概念
对于很多问题来说,直接计算全局的答案十分困难,但是如果将整个问题(例如图形)分割为许多较小的区间,单独求解是较为简单的,扫描线算法就可以解决此类问题。
复杂度:
O ( n 2 l o g ( n ) ) O(n^2log(n)) O(n2log(n))
线段树优化后:
O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))
1.2 扫描线求矩形的面积并
![图片.png](https://i-blog.csdnimg.cn/blog_migrate/3215cc89ab0cfa165c7fc14ff16f9b23.png)
将每个矩形的左右边界保存并排序,从左到右扫描一遍,求条线之间的面积之并即可
暴力的方法是处理到每个小区间时,枚举每个矩形,看是否处于当前的区间之中,然后对于当前这个小区间计算面积的并集
线段树优化的方法是将矩形的左边界看做+1,右边界看做-1操作,每次扫描到之后,就进行区间修改,在修改之前,先看所有权值大于0的区间是多少,将其乘上两条线的距离就是这一段区间的贡献
![图片.png](https://i-blog.csdnimg.cn/blog_migrate/1761f66d2e3356f0cd3addedd30f38cd.png)
线段树维护:
1.cnt代表当前区间整个被覆盖次数
2.len表示不考虑祖先节点cnt的前提下,cnt大于0的区间总长
线段树节点永远只向下看不向上看
此题的两个性质(只有求矩形才能用,如果换成平行四边形就不行了):
1.永远只考虑根节点的信息:query的时候不需要pushdown
2.所有操作都是成对出现且先加后减:modify的时候不需要pushdown
1.3 扫描线求三角形的面积并
不仅需要考虑顶点,还需要考虑每条线的交点:
这样分割出来的区间一定没有交点,且面积都被分成了多个连续的矩形:
所以求每个区间的矩形面积即可,复杂度为 O ( n 3 l o g ( n ) ) O(n^3log(n)) O(n3log(n)),因为每两个三角形都有可能有交点,所以比矩形的面积并多了一个n的复杂度
1.4 扫描线求矩形面积的交
这里讲解线段树优化的做法,只需要在求矩形面积并的基础上加以修改即可
我们要计算被覆盖两次或以上的部分面积,我们在线段树节点中增设了一个变量,more,其中len表示该该区间内被覆盖了1次或以上的长度,more表示被覆盖了2次或以上的长度
怎么计算最后的面积的?一样的道理,从左到右扫描矩形,然后看看t[1].more是多少,再乘上高度差。因为t[1]表示了总区间,而more表示被覆盖两次或以上的长度,即计算时我们忽略掉只被覆盖一次的长度
问题的关键变为怎么计算一个节点的more
分情况讨论
1.cnt>1 : 说明该区间被覆盖两次或以上,那么长度就可以直接计算,就是该区间的长度
剩下的情况就是cnt=1或cnt=0
2.先看叶子节点,因为是叶子没有孩子了,所以被覆盖两次或以上的长度就是0(无论cnt=1或cnt=0都是0,因为是叶子。。。)
3.不是叶子节点 ,且cnt=1.注意这里,cnt=1确切的意义是什么,应该是:可以确定这个区间被完全覆盖了1次,而有没有被完全覆盖两次或以上则不知道无法确定,那么怎么办呢,只要加上len[lch] + len[rch] 即,看看左右孩子区间被覆盖了一次或以上的长度,那么叠加在双亲上就是双亲被覆盖两次或以上的长度
3.不是叶子节点,且cnt=0,确切的意义应该是不完全不知道被覆盖的情况(不知道有没有被覆盖,被覆盖了几次,长度是多少都不知道),这种情况,只能由其左右孩子的信息所得:more[lch] + more[rch] , 即直接将左右孩子给覆盖了两次或以上的长度加起来,这样才能做到不重不漏
1.5 扫描线求矩形周长的并
和线段树求矩形周长的并类似,不过不需要乘高,需要注意的是,每次计算的是len[1]与上一次的len[1]的差值:
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/d73e563c565c632fafb75ec489cb9160.png)
但是这样只是求了横着的周长,所以分别做两次扫描线,求出x和y方向上的周长即可。
2.模板
2.1 求矩形的面积并
扫描线:
#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 1010;
int n;
PII l[N], r[N];
PII q[N];
LL range_area(int a, int b) {
int cnt = 0;
for (int i = 0; i < n; i++)
if (l[i].x <= a && r[i].x >= b) q[cnt++] = {
l[i].y, r[i].y};
if (!cnt) return 0;
sort(q, q + cnt);
LL res = 0;
int st = q[0].x, ed = q[0].y;
//区间合并
for (int i = 1; i < cnt; i++)
if (q[i].x <= ed)
ed = max(ed, q[i].y);
else {
res += ed - st;
st = q[i].x, ed = q[i].y;
}
res += ed - st;
return res * (b - a);
}
int main() {
scanf("%d", &n);
vector<int> xs;
for (int i = 0; i < n; i++) {
scanf("%d%d%d%d", &l[i].x, &l[i].y, &r[i].x, &r[i].y);
xs.push_back(l[i].x), xs.push_back(r[i].x);
}
sort(xs.begin(), xs.end());
LL res = 0;
for (int i = 0; i + 1 < xs.size(); i++)
//计算两条线之间的小矩形面积
if (xs[i] != xs[i + 1]) res += range_area(xs[i], xs[i + 1]);
printf("%lld\n", res);
return 0;
}
扫描线+线段树:
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n;
struct Segment {
double x, y1, y2;
int k;
bool operator<(const Segment &t) const {
return x < t.x; }
} seg[N * 2];
int cnt[N << 3]; // cnt[u]维护u的管理区间是否全部都被覆盖
double len[N << 3]; // len[u]维护u的管理区间内覆盖的长度
vector<double> ys;
int find(double y) {
return lower_bound(ys.begin(), ys.end(), y) - ys.begin(); }
void pushup(int u, int l, int r) {
if (cnt[u]) // 如果这段都被覆盖,那么有效的长度就是这段长度
len[u] = ys[r + 1] - ys[l];
else if (l != r) // 如果这段没有完全覆盖,那么就是左子区间被覆盖长度+右子区间被覆盖长度
len[u] = len[u << 1] + len[u << 1 | 1];
else // 要不然就是0
len[u] = 0;
}
void build(int u, int l, int r) {
len[u] = cnt[u] = 0;
if (l != r) {
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}
}
void modify(int u, int l, int r, int L, int R, int k) {
if (L <= l && r <= R) {
cnt[u] += k;
pushup(u, l, r);
} else {
int mid = l + r >> 1;
if (L <= mid) modify(u << 1, l, mid, L, R, k);
if (mid < R) modify(u << 1 | 1, mid + 1, r, L, R, k);
pushup(u, l, r);
}
}
int main() {
int T = 1;
while (scanf("%d", &n) != EOF) {
if (!n) break;
ys.clear();
for (int i = 0, j = 0; i < n; i++) {
double x1, y1, x2, y2;
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
seg[j++] = {
x1, y1, y2, 1}; // 矩形的左边界+1
seg[j++] = {
x2, y1, y2, -1}; // 右边界减1
ys.push_back(y1), ys.push_back(y2); // y离散化
}
// 排序去重
sort(ys.begin(), ys.end());
ys.erase(unique(ys.begin(), ys.end()), ys.end());
build(1, 0<