【模板】扫描线

发布时间 2023-11-16 13:51:33作者: onlyblues

【模板】扫描线

题目描述

求 $n$ 个四边平行于坐标轴的矩形的面积并。

输入格式

第一行一个正整数 $n$。

接下来 $n$ 行每行四个非负整数 $x_1, y_1, x_2, y_2$,表示一个矩形的四个端点坐标为 $(x_1, y_1),(x_1, y_2),(x_2, y_2),(x_2, y_1)$。

输出格式

一行一个正整数,表示 $n$ 个矩形的并集覆盖的总面积。

样例 #1

样例输入 #1

2
100 100 200 200
150 150 250 255

样例输出 #1

18000

提示

对于 $20\%$ 的数据,$1 \le n \le 1000$。  
对于 $100\%$ 的数据,$1 \le n \le {10}^5$,$0 \le x_1 < x_2 \le {10}^9$,$0 \le y_1 < y_2 \le {10}^9$。

 

解题思路

  扫描线经典问题,需要用线段树来进行优化。记录这篇题解主要是介绍常规的懒标记线段树写法,大部分题解给出的都是不下传懒标记的做法,然而一年多过去了我还是理解不了这样做为什么是正确的,所以总是忘掉然后回来复习结果还是看不懂。然后昨天又碰到了扫描线相关的题目,复习的时候看到了这篇题解,给出线段树的另外一种写法,现在是终于知道怎么用线段树求矩形并集的面积了。

  还是先介绍一下求矩形并集的面积的问题,就是二维平面上有若干个四边平行于坐标轴的矩形,他们之间可能会有重叠,问这些矩形的并集的面积是多少。

  看上去好像是容斥原理的问题,不过这样做的话会相当麻烦。扫描线的思想就是用平行于竖轴的水平线,根据矩形的竖边将整个不规则图形划分为若干个区域,如下图:

  通过观察可以知道,两条竖线 $x_i$ 和 $x_{i+1}$ 之间有若干个规则的矩形,这些矩形的长都是 $x_{i+1} - x_i$,宽的总和记作 $y_i$。要计算 $n$ 个矩形并集的面积,等价于分别计算 $2n -1$ 条竖线间的矩形的面积再求和,即 $S = \sum\limits_{i=1}^{2n-1}{y_i \cdot (x_{i+1} - x_i)}$。

  问题是如何得到每个 $y_i$?我们用一个数组来记录竖轴上每个位置(线段)被覆盖的次数(当然由于题目中 $y_i$ 的值域很大,需要离散化),那么有被覆盖的位置的总长度就是 $y_i$ 了。

  假设矩形的位置用左下角 $(x_1, y_1)$ 和右上角 $(x_2, y_2)$ 来表示,那么用四元组 $(x_1, y_1, y_2, 1)$ 和 $(x_2, y_1, y_2, -1)$ 来表示矩形的两条竖边。以第一个参数 $x$ 为关键字对四元组从小到大排序,那么就会得到上图中从左到右的 $2n$ 条竖线。依次遍历每条竖线,如果第四个参数是 $1$ 则在数组中把 $y_1 \sim y_2$ 的位置覆盖一次,否则是 $-1$ 则把 $y_1 \sim y_2$ 的位置删除一次覆盖。这样就可以维护出每个 $y_i$,不过在考虑对纵坐标离散化后这种暴力做法的时间复杂度是 $O(n^2)$。

  由于涉及到区间加的问题,因此可以考虑用线段树来维护。这里就不给出大部分题解用到的做法了,下面介绍用常规懒标记线段树的写法,并不复杂而且更容易理解。

  在这个问题中,线段树本质上是维护至少被覆盖一次的线段的数量(总长度),修改的操作只有区间加,并且保证任意修改后不会出现负数。因此线段树节点只需维护如下的信息:

struct Node {
    int l, r, mn, s, add;
};

  其中 $\text{mn}$ 是 $[l,r]$ 这个区间中所有线段被覆盖的次数的最小值。$s$ 是被覆盖次数为 $\text{mn}$ 的所有线段的长度。$\text{add}$ 是区间加的懒标记。

  对于某个区间中的线段,

  1. 如果 $\text{mn} > 0$,意味着该区间中所有的线段都被覆盖,那么被覆盖的线段总长度就是区间的长度。
  2. 否则 $\text{mn} = 0$,意味着存在某些线段没有被覆盖,那么被覆盖的线段的总长度就是区间的长度减去 $s$。

  在修改(维护的区间被修改的区间完全覆盖)和懒标记下传的操作中,如果区间加的值是 $c$,只需让 $\text{mn} \gets \text{mn} + c$,$\text{add} \gets \text{add} + c$,而不用改变 $s$,这是因为对区间的所有线段都覆盖一次后,覆盖次数最小的线段还是之前的那些。

  AC 代码如下,时间复杂度为 $O(n \log{n})$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 2e5 + 10;

struct Seg {
    int x, y1, y2, c;
}p[N];
int xs[N], sz;
struct Node {
    int l, r, mn, s, add;
}tr[N * 4];

void build(int u, int l, int r) {
    tr[u] = {l, r, 0, 0, 0};
    if (l == r) {
        tr[u].s = xs[r + 1] - xs[l];
    }
    else {
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        tr[u].s = tr[u << 1].s + tr[u << 1 | 1].s;
    }
}

void pushdown(int u) {
    if (tr[u].add) {
        tr[u << 1].mn += tr[u].add;
        tr[u << 1].add += tr[u].add;
        tr[u << 1 | 1].mn += tr[u].add;
        tr[u << 1 | 1].add += tr[u].add;
        tr[u].add = 0;
    }
}

void modify(int u, int l, int r, int c) {
    if (tr[u].l >= l && tr[u].r <= r) {
        tr[u].mn += c;
        tr[u].add += c;
    }
    else {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, c);
        if (r >= mid + 1) modify(u << 1 | 1, l, r, c);
        tr[u].mn = min(tr[u << 1].mn, tr[u << 1 | 1].mn);
        tr[u].s = 0;
        if (tr[u << 1].mn == tr[u].mn) tr[u].s += tr[u << 1].s;
        if (tr[u << 1 | 1].mn == tr[u].mn) tr[u].s += tr[u << 1 | 1].s;
    }
}

int find(int x) {
    int l = 1, r = sz;
    while (l < r) {
        int mid = l + r >> 1;
        if (xs[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return l;
}

int main() {
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        int x1, y1, x2, y2;
        scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
        p[i << 1] = {x1, y1, y2, 1};
        p[i << 1 | 1] = {x2, y1, y2, -1};
        xs[++sz] = y1, xs[++sz] = y2;
    }
    sort(xs + 1, xs + sz + 1);
    sz = unique(xs + 1, xs + sz + 1) - xs - 1;
    sort(p, p + 2 * n, [&](Seg &a, Seg &b) {
        return a.x < b.x;
    });
    build(1, 1, sz - 1);
    LL ret = 0;
    for (int i = 0; i < 2 * n - 1; i++) {
        modify(1, find(p[i].y1), find(p[i].y2) - 1, p[i].c);
        LL len = xs[sz] - xs[1] - !tr[1].mn * tr[1].s;
        ret += len * (p[i + 1].x - p[i].x);
    }
    printf("%lld", ret);
    
    return 0;
}

 

参考资料

  题解 P5490 【【模板】扫描线】:https://www.luogu.com.cn/blog/xuxuxuxuxu/solution-p5490

  AcWing 247. 亚特兰蒂斯(算法提高课):https://www.acwing.com/video/653/