进阶数据结构应用-线段树扫描线

目录

题目-亚特兰蒂斯


问题分析

计算面积的并集

类似于扫描的思想, 只要是 y y y不连续, 将面积从左到右做细分

设 h 1 = t 0 + t 1 h_1= t_0 + t_1 h1=t0+t1, 同理 h 2 h_2 h2就是下一段的 x x x的差值, 那么总的覆盖面积 就是
∑ i = 1 n h i ⋅ ( y i − y i − 1 ) \sum {i = 1} ^ n h_i \cdot (y_i - y{i - 1}) i=1∑nhi⋅(yi−yi−1)

将矩形的边做成带权值的线段 , 扫描到的时候 + 1 + 1 +1, 离开的时候 − 1 - 1 −1

映射到 x x x上, x x x上建立线段树, 检查当前 x x x轴线段树上有多少个区间的值大于 0 0 0

当前这一段 x > 0 x > 0 x>0等价与当前这段区间被 x x x个矩形覆盖, 需要累加到答案当中

抽象为线段树的两个操作

  • 将区间 [ L , R ] [L, R] [L,R]加上 k k k
  • 求整个区间中权值大于 0 0 0的区间总长度

线段树维护的节点信息

  • c n t cnt cnt当前区间被覆盖的次数
  • s u m sum sum表示不考虑祖先节点 c n t cnt cnt情况下, c n t > 0 cnt > 0 cnt>0的权值之和

扫描线有如下性质

  1. 只查询根节点 , 因此查询过程中直接返回不会递归 , 因此不会用到push down操作
  2. 修改的时候也不需要push down, 因为所有的区间操作 都是成对出现 , 并且区间先 + + +后 − - −, 对于每个矩形来说, 只会 + + +一次, − - −一次, 并且修改的区间节点 是一致的, 因此不需要push down操作

具体的来说, 假设线段树修改到了上面红色的节点(子节点), 但是每次查询只查询根节点(蓝色), 只会用到根节点的两个子节点 的信息

在修改完子节点回溯的过程中向上更新(添加减少的都是相同的子节点的值), 也就是一个节点所代表的区间被覆盖次数不需要继承父节点信息

并且不会查询到子区间分裂的情况 , 因此不需要push down操作

算法步骤

  • 读入坐标
  • 将矩形分为两类, 一类是 + 1 +1 +1, 一类是 − 1 -1 −1, 同时将 y y y离散化
  • 假设离散化后有 m m m个点, 实际是 m − 1 m - 1 m−1个区间, 线段树每个节点存储的是区间而不是点 , 因此线段树的区间索引是 0 ∼ m − 2 0 \sim m - 2 0∼m−2
  • 因此对于线段树节点假设是 [ l , r ] [l, r] [l,r], 对应实际的点的坐标是 [ l , r + 1 ] [l, r + 1] [l,r+1], 因此如果当前线段树维护的区间被覆盖(cnt != 0), 实际被覆盖的长度tr[u].sum = (nums[r + 1] - nums[l])

代码实现

cpp 复制代码
#include <bits/stdc++.h>

const int N = 1e5 + 10;
using namespace std;

int T = 1, n;
struct Seg {
    double x, y1, y2;
    int val;
    bool operator< (const Seg &s) const {
        return x < s.x;
    }
} segs[N << 1];
struct Node {
    int l, r;
    int cnt;
    double sum;
} tr[N << 3];
vector<double> nums;

int find(double x) {
    return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}

void pushup(int u) {
    if (tr[u].cnt) {
        // 因为线段树[l, r]存储的是区间下标, 对应的离散化后点的下标是[l + 1, r]
        tr[u].sum = nums[tr[u].r + 1] - nums[tr[u].l];
    }
    // 当前区间未被覆盖, 实际的被覆盖的长度等于子节点覆盖的长度
    else if (tr[u].l != tr[u].r) {
        tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
    }
    // 当前区间未被覆盖并且也不存在子节点
    else {
        tr[u].sum = 0;
    }
}

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

void modify(int u, int ql, int qr, int val) {
    if (tr[u].l >= ql && tr[u].r <= qr) {
        tr[u].cnt += val;
        // 当前值或者两个儿子的值计算一下
        pushup(u);
        return;
    }

    int mid = tr[u].l + tr[u].r >> 1;
    if (ql <= mid) modify(u << 1, ql, qr, val);
    if (qr > mid) modify(u << 1 | 1, ql, qr, val);

    pushup(u);
}


void solve() {
    nums.clear();

    int cnt = 0;
    for (int i = 0; i < n; ++i) {
        double x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        segs[cnt++] = {x1, y1, y2, 1};
        segs[cnt++] = {x2, y1, y2, -1};
        nums.push_back(y1), nums.push_back(y2);
    }

    sort(segs, segs + cnt);
    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());

    int m = nums.size();
    build(1, 0, m - 2);

    double ans = 0;
    for (int i = 0; i < cnt; ++i) {
        if (i > 0) ans += tr[1].sum * (segs[i].x - segs[i - 1].x);
        Seg &s = segs[i];
        modify(1, find(s.y1), find(s.y2) - 1, s.val);
    }

    printf("Test case #%d\n", T++);
    printf("Total explored area: %.2lf\n\n", ans);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    while (cin >> n, n) solve();

    return 0;
}
相关推荐
leoufung1 小时前
LeetCode 98 Validate Binary Search Tree 深度解析
算法·leetcode·职场和发展
水木姚姚1 小时前
C++ begin
开发语言·c++·算法
浅川.251 小时前
xtuoj 素数个数
数据结构·算法
jyyyx的算法博客1 小时前
LeetCode 面试题 16.18. 模式匹配
算法·leetcode
uuuuuuu1 小时前
数组中的排序问题
算法
Stream1 小时前
加密与签名技术之密钥派生与密码学随机数
后端·算法
Stream1 小时前
加密与签名技术之哈希算法
后端·算法
少许极端2 小时前
算法奇妙屋(十五)-BFS解决边权为1的最短路径问题
数据结构·算法·bfs·宽度优先·队列·图解算法·边权为1的最短路径问题
c骑着乌龟追兔子2 小时前
Day 27 常见的降维算法
人工智能·算法·机器学习