P5490 【模板】扫描线

题解

学习博客↓

题解 P5490 【【模板】扫描线】

【学习笔记】扫描线

扫描线建议纸上模拟一遍,这样才更加清晰

记几个重点:

假设从下往上扫描,

每条边按照 y y y 值坐标从小到大排序,标记其是矩形的上底(标记为+1),还是下底(标记为-1)

这样,对于每个矩形,扫描线总是会先碰到下边,然后再碰到上边

建立一棵线段树,其每个端点维护一条线段(也就是一个区间)的信息:
1.该线段被覆盖了多少次(被多少个矩形所覆盖)- sum
2.该线段内被整个图形所截的长度是多少 - len

不过不同于以往的线段树,这里的线段树里每个节点表示的是一条线段,长度 > 0 >0 >0 ,区间 [ l , r ] [l,r] [l,r]表示的是 X l ∼ r + 1 X_{l\sim r+1} Xlr+1,用图表示就是长这样子
在这里插入图片描述


在这里插入图片描述


代码

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int n;
int X[N << 1];
ll x1, x2, y1, y2; // 有y1 所以避开了bits头

namespace ScanningLine {//扫描线板子
#define lson (rt<<1)
#define rson (rt<<1|1)

    struct ScanLine { //扫描线
        ll l, r, h; // 左右端点 y轴坐标
        int mark; // 用于保存权值1/-1 从下往上扫描 下底+1 上底-1
        bool operator<(const ScanLine &b) const {
            return h < b.h; //根据y轴坐标从小到大排序
        }
    } line[N << 1];
    //扫描线从矩形的下底扫到矩形的上底

    struct SegTree {
        int l, r;
        int sum; //统计的是
        ll len;
    } tree[N << 2];

    void pushup(int rt) {
        int l = tree[rt].l, r = tree[rt].r;
        if (tree[rt].sum) { //不为0 说明该区间被线段覆盖了
            tree[rt].len = X[r + 1] - X[l];//更新长度
        } else {
            tree[rt].len = tree[lson].len + tree[rson].len; //合并儿子信息
        }
    }

    void build(int l, int r, int rt) {
        //区间[l,r] 表示线段[l,r+1] <- 没写错
        //因为线段树每个节点表示的是一个线段 要有长度 像[x,x]就不可以表示成一个点
        tree[rt] = {l, r, 0, 0};
        if (l == r) {
            return;
        }
        int mid = l + r >> 1;
        build(l, mid, lson);
        build(mid + 1, r, rson);
    }

    void update(int L, int R, int c, int rt) {
        int l = tree[rt].l, r = tree[rt].r;
        if (X[r + 1] <= L || R <= X[l]) return;
        //因为线段树节点表示的特殊性 需要额外的判断 当前区间是否是我们需要的那个

        if (L <= X[l] && X[r + 1] <= R) {//能到这一步说明有 一部分 线段是在该区间内的
            tree[rt].sum += c; //对线段打标记
            pushup(rt);
            return;
        }
        update(L, R, c, lson);
        update(L, R, c, rson);
        pushup(rt);
    }
}
using namespace ScanningLine;

int main() {
    ios::sync_with_stdio(0);

    cin >> n;//n个矩形
    for (int i = 1; i <= n; ++i) {
        cin >> x1 >> y1 >> x2 >> y2; // 左下角 右上角
        X[2 * i - 1] = x1;
        X[2 * i] = x2;

        line[2 * i - 1] = {x1, x2, y1, 1};
        line[2 * i] = {x1, x2, y2, -1};
        //一条线段含两个端点 一个矩形的上下边都需要扫描线扫过
    }

    n <<= 1;
    sort(line + 1, line + n + 1);
    sort(X + 1, X + n + 1);
    int tot = unique(X + 1, X + n + 1) - X - 1;

    build(1, tot - 1, 1);//节点[1,tot-1]表示的线段是[1,tot]

    ll ans = 0;
    for (int i = 1; i < n; ++i) {
        update(line[i].l, line[i].r, line[i].mark, 1); // 线段的起点 线段的终点 上底/下底 根节点
        ans += tree[1].len * (line[i + 1].h - line[i].h); //tree[1].len表示扫描线扫到的线段的长度
    }
    cout << ans << endl;

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值