什么是扫描线?

扫描线就是一条线在整个图上扫来扫去,它一般被用来解决图形面积,周长,以及二维数点等问题。

下面我们用例题来引入。

P5490 【模板】扫描线 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

我们对于这种题有三种做法

  1. 暴力的进行覆盖扫描

  2. 容斥

  3. 线段树

第一种做法,你会 T 或者 MLE 或者两者兼得;第二种做法,你的脑子会 RE ;第三种做法,就是我们今天的主角——扫描线。

过程

扫描线的大体思路是这样的。

「学习笔记」扫描线-LMLPHP

这个动图来自 ,在线段树中,我们要维护这一条线段的左右端点(即起始和终止位置),以及这个线段的高度;线段树中记录的是这个节点对应的线段的左端点在数组中的标号、右端点在数组中的编号,以及覆盖标记和覆盖长度。

struct node {
    int val;
    ll L, R, H;
    // val 该线段是否存在
    // L, R 左右端点
    // H 高度
} line[N << 1];

struct seg {
    int L, R, tag;
    ll sum;
    // L, R 左右短点
    // sum 被覆盖的长度
    // tag 是否被完全覆盖
} t[N << 3];

我们在输入的时候要记录一个图形的开始位置和结束位置。

for (int i = 1; i <= n; ++ i) {
    int x_1 = read<ll>(), y_1 = read<ll>(), x_2 = read<ll>(), y_2 = read<ll>();
    line[i] = node{1, x_1, x_2, y_1};
    line[i + n] = node{-1, x_1, x_2, y_2};
    s[i] = x_1, s[i + n] = x_2;
}

把右端点的对应关系给改了下,于是就兼容了。

随后是我们的查询函数,相信聪明的你可以看懂。

void pushup(int u) {
    if (t[u].tag > 0) { // 是否被完全覆盖
        t[u].sum = s[t[u].R + 1] - s[t[u].L];
        return ;
    }
    if (t[u].L != t[u].R) { // 如果不是叶子节点
        t[u].sum = t[ls].sum + t[rs].sum;
    } else { // 如果是叶子节点
        t[u].sum = 0;
    }
    return ;
}

void Find(int u, ll l, ll r, int v) {
    if (s[t[u].R + 1] <= l || s[t[u].L] >= r) {
        return ;
    }
    if (l <= s[t[u].L] && s[t[u].R + 1] <= r) {
        t[u].tag += v;
        pushup(u);
        return ;
    }
    if (t[u].L == t[u].R) { // 当前已经到了叶子节点
        return ;
    }
    Find(ls, l, r, v);
    Find(rs, l, r, v);
    pushup(u);
}

总代码:

//The code was written by yifan, and yifan is neutral!!!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define ls (u << 1)
#define rs (u << 1 | 1)
#define mid ((l + r) >> 1)

template<typename T>
inline T read() {
    T x = 0;
    bool fg = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        fg |= (ch == '-');
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return fg ? ~x + 1 : x;
}

const int N = 1e5 + 5;

int n;
ll s[N << 1];

struct node {
    int val;
    ll L, R, H;
    // val 该线段是否存在
    // L, R 左右端点
    // H 高度
} line[N << 1];

struct seg {
    int L, R, tag;
    ll sum;
    // L, R 左右短点
    // sum 被覆盖的长度
    // tag 是否被完全覆盖
} t[N << 3];

void pushup(int u) {
    if (t[u].tag > 0) {
        t[u].sum = s[t[u].R + 1] - s[t[u].L];
        return ;
    }
    if (t[u].L != t[u].R) {
        t[u].sum = t[ls].sum + t[rs].sum;
    } else {
        t[u].sum = 0;
    }
    return ;
}

void build(int u, int l, int r) {
    t[u].L = l, t[u].R = r;
    t[u].sum = t[u].tag = 0;
    if (l == r) {
        return ;
    }
    build(ls, l, mid);
    build(rs, mid + 1, r);
}

void Find(int u, ll l, ll r, int v) {
    if (s[t[u].R + 1] <= l || s[t[u].L] >= r) {
        return ;
    }
    if (l <= s[t[u].L] && s[t[u].R + 1] <= r) {
        t[u].tag += v;
        pushup(u);
        return ;
    }
    if (t[u].L == t[u].R) {
        return ;
    }
    Find(ls, l, r, v);
    Find(rs, l, r, v);
    pushup(u);
}

int main() {
    n = read<int>();
    for (int i = 1; i <= n; ++ i) {
        int x_1 = read<ll>(), y_1 = read<ll>(), x_2 = read<ll>(), y_2 = read<ll>();
        line[i] = node{1, x_1, x_2, y_1};
        line[i + n] = node{-1, x_1, x_2, y_2};
        s[i] = x_1, s[i + n] = x_2;
    }
    n <<= 1;
    sort(s + 1, s + n + 1);
    int tot = unique(s + 1, s + n + 1) - s - 1;
    sort(line + 1, line + n + 1, [](node a, node b) -> bool {
        return a.H < b.H;
    });
    build(1, 1, tot - 1);
    ll ans = 0;
    for (int i = 1; i < n; ++ i) {
        Find(1, line[i].L, line[i].R, line[i].val);
        ans += t[1].sum * (line[i + 1].H - line[i].H);
    }
    cout << ans << '\n';
    return 0;
}

再来一道例题。

P8648 [蓝桥杯 2017 省 A] 油漆面积 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

//The code was written by yifan, and yifan is neutral!!!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define ls (u << 1)
#define rs (u << 1 | 1)
#define mid ((l + r) >> 1)

template<typename T>
inline T read() {
    T x = 0;
    bool fg = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        fg |= (ch == '-');
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return fg ? ~x + 1 : x;
}

const int N = 1e4 + 5;

int n, tot;
int s[N << 1];

struct Line {
    int L, R, H, val;
} line[N << 1];

struct seg {
    int L, R, tag;
    ll len;
} t[N << 3];

void build(int u, int l, int r) {
    t[u].L = l, t[u].R = r;
    t[u].tag = t[u].len = 0;
    if (l == r) {
        return ;
    }
    build(ls, l, mid);
    build(rs, mid + 1, r);
}

void pushup(int u) {
    if (t[u].tag) {
        t[u].len = s[t[u].R + 1] - s[t[u].L];
        return ;
    }
    if (t[u].L == t[u].R) {
        t[u].len = 0;
    } else {
        t[u].len = t[ls].len + t[rs].len;
    }
}

void Find(int u, int l, int r, int v) {
    if (s[t[u].R + 1] <= l || r <= s[t[u].L]) {
        return ;
    }
    if (l <= s[t[u].L] && s[t[u].R + 1] <= r) {
        t[u].tag += v;
        pushup(u);
        return ;
    }
    if (t[u].L == t[u].R) {
        return ;
    }
    Find(ls, l, r, v);
    Find(rs, l, r, v);
    pushup(u);
}

int main() {
    n = read<int>();
    for (int i = 1; i <= n; ++ i) {
        int x_1 = read<int>(), y_1 = read<int>(), x_2 = read<int>(), y_2 = read<int>();
        line[i] = Line{x_1, x_2, y_1, 1};
        line[i + n] = Line{x_1, x_2, y_2, -1};
        s[i] = x_1, s[i + n] = x_2;
    }
    n <<= 1;
    sort(s + 1, s + n + 1);
    tot = unique(s + 1, s + n + 1) - s - 1;
    build(1, 1, tot - 1);
    sort(line + 1, line + n + 1, [](Line a, Line b) -> bool {
        return a.H < b.H;
    });
    ll ans = 0;
    for (int i = 1; i < n; ++ i) {
        Find(1, line[i].L, line[i].R, line[i].val);
        ans += t[1].len * (line[i + 1].H - line[i].H);
    }
    cout << ans << '\n';
    return 0;
}

求周长并

求周长并比求面积更复杂,周长还要考虑竖着的线段的长度。

对于横边,相邻两次修改的区间覆盖长度差(就是 t[1].len 的差)加起来就是答案(不理解的自己想办法理解,反正我不理解);

对于竖边,我们只需要记录整个区间有多少个端点(包含在线段内不算),然后用它乘上相邻两次修改的高度差即可。

代码:

//The code was written by yifan, and yifan is neutral!!!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define ls (u << 1)
#define rs (u << 1 | 1)
#define mid ((l + r) >> 1)

template<typename T>
inline T read() {
    T x = 0;
    bool fg = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        fg |= (ch == '-');
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return fg ? ~x + 1 : x;
}

const int N = 5010;

int n;
int s[N << 1];

struct Line {
    int L, R, H, val;
} line[N << 1];

struct seg {
    int L, R, tag, cnt;
    ll len;
    bool lc, rc;
} t[N << 3];

void build(int u, int l, int r) {
    t[u].L = l, t[u].R = r;
    t[u].tag = t[u].len = t[u].cnt = 0;
    t[u].lc = t[u].rc = false;
    if (l == r) {
        return ;
    }
    build(ls, l, mid);
    build(rs, mid + 1, r);
}

void pushup(int u) {
    if (t[u].tag) {
        t[u].len = s[t[u].R + 1] - s[t[u].L];
        t[u].lc = t[u].rc = true;
        t[u].cnt = 1;
        return ;
    }
    if (t[u].L == t[u].R) {
        t[u].len = 0;
        t[u].lc = t[u].rc = false;
        t[u].cnt = 0;
    } else {
        t[u].len = t[ls].len + t[rs].len;
        t[u].lc = t[ls].lc, t[u].rc = t[rs].rc;
        t[u].cnt = t[ls].cnt + t[rs].cnt;
        if (t[ls].rc && t[rs].lc) {
            -- t[u].cnt;
        }
    }
}

void Find(int u, int l, int r, int v) {
    if (r <= s[t[u].L] || s[t[u].R + 1] <= l) {
        return ;
    }
    if (l <= s[t[u].L] && s[t[u].R + 1] <= r) {
        t[u].tag += v;
        pushup(u);
        return ;
    }
    if (t[u].L == t[u].R) {
        return ;
    }
    Find(ls, l, r, v);
    Find(rs, l, r, v);
    pushup(u);
}

int main() {
    n = read<int>();
    for (int i = 1, x_1, y_1, x_2, y_2; i <= n; ++ i) {
        x_1 = read<int>(), y_1 = read<int>(), x_2 = read<int>(), y_2 = read<int>();
        line[i] = Line{x_1, x_2, y_1, 1};
        line[i + n] = Line{x_1, x_2, y_2, -1};
        s[i] = x_1, s[i + n] = x_2;
    }
    n <<= 1;
    sort(s + 1, s + n + 1);
    int tot = unique(s + 1, s + n + 1) - s - 1;
    build(1, 1, tot - 1);
    sort(line + 1, line + n + 1, [](Line a, Line b) -> bool {
        if (a.H == b.H) return a.val > b.val;
        return a.H < b.H;
    });
    ll ans = 0, pre = 0;
    for (int i = 1; i < n; ++ i) {
        Find(1, line[i].L, line[i].R, line[i].val);
        ans += abs(pre - t[1].len);
        pre = t[1].len;
        ans += 2 * t[1].cnt * (line[i + 1].H - line[i].H);
    }
    ans += line[n].R - line[n].L;
    cout << ans << '\n';
    return 0;
}

上模板题!

P1856 [IOI1998] [USACO5.5] 矩形周长Picture - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

08-06 15:07