首页 > 其他分享 >【模板】扫描线

【模板】扫描线

时间:2023-11-16 13:55:55浏览次数:36  
标签:le 覆盖 int text 线段 扫描线 矩形 模板

【模板】扫描线

题目描述

求 $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/

标签:le,覆盖,int,text,线段,扫描线,矩形,模板
From: https://www.cnblogs.com/onlyblues/p/17836031.html

相关文章

  • c#调用Bartender标签模板打印
    Bartender标签打印软件挺好用的,模板可视化,参数也好调整,我用的是这个版本 先在电脑上装好Bartender软件然后在VS项目中,添加引用,选择COM组件,搜索Bartender,确定引用BarTender10.1 在项目中创建BarTenderPrint类///<summary>///打印标签类///</summary......
  • 委托使用-模板方法
    usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;namespace_03_委托的使用_模板方法{//模板方法——“借用”指定的外部方法来产生结果//优点:实现了代码的重复使用,提高代码效率//特点://1.相当于填空题//2.......
  • Java将SQL解析为SQL模板
    /***获取sql模板*/publicStringextraSqlTemplate(StringsqlContent){if(StringUtils.isBlank(sqlContent)){return"";}String[]sqlContentArr=sqlContent.split("");String......
  • 客户端首屏渲染时首先拿到空的html模板,之后继续发起数据请求。而服务器端渲染只需要请
    客户端首屏渲染时首先拿到空的html模板,之后继续发起数据请求。而服务器端渲染只需要请求一次,服务器会将请求的数据放在html模板中一起返回。服务器端渲染耗费流量,局部页面的变化也需要重新请求完整的页面客户端渲染就可以采用SPA,能实现局部组件的更新,服务器端渲染回来的就是整个......
  • 【转载】计算几何模板
    转自https://blog.csdn.net/k_l_c_/article/details/51992298?spm=1001.2014.3001.5502两点之间距离判断两点是否重合叉积//可判断点在线段或直线的哪一侧点积判断点p是否在线段l上返回点p以点o为圆心逆时针旋转alpha(单位:弧度)后所在的位置返回顶角在o点,起始边为os,终止......
  • 浅谈扫描线
    Preface本文部分题目摘自lxl的数据结构系列课件由于工程量巨大,难免会出现错字、漏字的情况,如果影响到了您的阅读体验,还请私信我,我会在第一时间修复。本文建议大家有一定的数据结构基础后再来阅读/heart。个人感觉扫描线不是难点,难点在于怎么抽象模型。首先需要明白一个东......
  • 如何自定义TAPD缺陷模板?
    一、引言现在不少公司使用TAPD,创建缺陷时,系统默认模板是一片空白。其实缺陷有较为固定的模板,设置好模板,就可以免去每次重复敲一些文字。二、设置模板进入设置的路径:设置--》应用设置--》缺陷--》显示设置--》创建页面模板--》新增创建页面模板进入页面后,根据自己的需要,编......
  • 模板匹配和霍夫变换
    ......
  • 开发现代化的.NetCore控制台程序:(2)创建一个C#项目模板
    前言上一篇文章(开发一个现代化的.NetCore控制台程序,包含依赖注入/配置/日志等要素)介绍了开发现代化的.NetCore控制台程序的细节,但这还不够,我又创建了一个脚手架模板,并命名为FluentConsole.Templates,可以方便的创建「现代化控制台应用」。源码地址:https://github.com/Deali-A......
  • Python进行多线程爬取数据通用模板
    首先,我们需要导入所需的库,包括requests和BeautifulSoup。requests库用于发送HTTP请求,BeautifulSoup库用于解析HTML文档。importrequestsfrombs4importBeautifulSoup然后,我们需要定义一个函数来发送HTTP请求并返回响应。在这个函数中,我们使用requests库的get方法来发送一个GET......