扫描线离散化线段树解决矩形面积并-洛谷P5490

https://www.luogu.com.cn/problem/P5490

题目描述

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

输入格式

第一行一个正整数 n n n

接下来 n n n 行每行四个非负整数 x 1 , y 1 , x 2 , y 2 x_1, y_1, x_2, y_2 x1,y1,x2,y2,表示一个矩形的四个端点坐标为 ( x 1 , y 1 ) , ( x 1 , y 2 ) , ( x 2 , y 2 ) , ( x 2 , y 1 ) (x_1, y_1),(x_1, y_2),(x_2, y_2),(x_2, y_1) (x1,y1),(x1,y2),(x2,y2),(x2,y1)

输出格式

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

输入输出样例 #1

输入 #1

2
100 100 200 200
150 150 250 255

输出 #1

18000

说明/提示

对于 20 % 20\% 20% 的数据, 1 ≤ n ≤ 1000 1 \le n \le 1000 1n1000
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 10 5 1 \le n \le {10}^5 1n105 0 ≤ x 1 < x 2 ≤ 10 9 0 \le x_1 < x_2 \le {10}^9 0x1<x2109 0 ≤ y 1 < y 2 ≤ 10 9 0 \le y_1 < y_2 \le {10}^9 0y1<y2109

🚀 C++ 代码实现

#include <iostream>
#include <vector>
#include <algorithm>using namespace std;struct Event {int x, y1, y2, type; // x 坐标, y 起点, y 终点, type=1(加入), type=-1(删除)Event(int x, int y1, int y2, int type) : x(x), y1(y1), y2(y2), type(type) {}
};struct SegmentTree {// y 轴区间被覆盖次数机覆盖长度vector<int> cnt, len;// 存储离散化后的 y 坐标值,用于映射 y 轴上的真实值到线段树的索引vector<int> y_coords;SegmentTree(int size) {cnt.resize(size * 4);len.resize(size * 4);}void build(int l, int r, int idx) {cnt[idx] = len[idx] = 0;if (l + 1 == r) return;int mid = (l + r) / 2;build(l, mid, idx * 2);build(mid, r, idx * 2 + 1);}void update(int jobl, int jobr, int val, int l, int r, int idx) {if (jobl >= r || jobr <= l) return;if (jobl <= l && r <= jobr) {cnt[idx] += val;} else {int mid = (l + r) / 2;update(jobl, jobr, val, l, mid, idx * 2);update(jobl, jobr, val, mid, r, idx * 2 + 1);}// 计算当前区间的 y 方向被覆盖的长度if (cnt[idx] > 0) {len[idx] = y_coords[r] - y_coords[l];  // 计算该段区间长度} else {len[idx] = (l + 1 == r) ? 0 : (len[idx * 2] + len[idx * 2 + 1]);  // 合并子区间}}
};int main() {int n;cin >> n;vector<Event> events;vector<int> y_coords;// 读取矩形数据for (int i = 0; i < n; i++) {int x1, y1, x2, y2;cin >> x1 >> y1 >> x2 >> y2;events.emplace_back(x1, y1, y2, 1);  // 左边界events.emplace_back(x2, y1, y2, -1); // 右边界y_coords.push_back(y1);y_coords.push_back(y2);}// 对 y 坐标进行离散化sort(y_coords.begin(), y_coords.end());y_coords.erase(unique(y_coords.begin(), y_coords.end()), y_coords.end());// 给事件按照 x 轴排序sort(events.begin(), events.end(), [](const Event &a, const Event &b) {return a.x < b.x;});// 初始化线段树SegmentTree segTree(y_coords.size());segTree.y_coords = y_coords;segTree.build(0, y_coords.size() - 1, 1);long long area = 0;for (size_t i = 0; i < events.size() - 1; i++) {int x = events[i].x;int y1 = lower_bound(y_coords.begin(), y_coords.end(), events[i].y1) - y_coords.begin();int y2 = lower_bound(y_coords.begin(), y_coords.end(), events[i].y2) - y_coords.begin();segTree.update(y1, y2, events[i].type, 0, y_coords.size() - 1, 1);// 计算面积增量int dx = events[i + 1].x - x;area += 1LL * dx * segTree.len[1];  // 累加当前 x 范围的面积}cout << area << endl;return 0;
}

🌟 解法思路:扫描线 + 线段树

核心思想:

  1. 转换为扫描线问题

    • 把每个矩形拆成两条竖直边(左边界 + 右边界)。
    • 每条边记录 x 坐标、y 区间以及是左边界(加入)还是右边界(删除)
  2. x 坐标排序

    • 依次处理 x 变化的位置,维护 y 方向的覆盖情况。
  3. 使用线段树维护 y 方向的覆盖长度

    • 统计当前 x 坐标下 y 方向被覆盖的总长度。
    • x 变化时,用 dx * covered_length 计算新增的面积。

📌 代码解析

  1. 读取输入并创建事件

    • 每个矩形 (x1, y1, x2, y2) 被拆成两个事件:
      • 左边界(x1, y1, y2, +1)
      • 右边界(x2, y1, y2, -1)
  2. 离散化 y

    • y 轴可能范围很大,使用排序+去重y 压缩成 [0, m-1] 范围。
  3. 排序事件

    • 按照 x 轴排序,保证扫描顺序是从左到右。
  4. 扫描线遍历

    • 遍历 x 方向的边界事件,更新 y 方向的覆盖长度。
    • 计算当前 x 范围的面积增量 dx * covered_length
  5. 线段树维护 y 方向覆盖情况

    • update(y1, y2, type) 更新 cnt 计数。
    • len[1] 存储 y 方向被覆盖的总长度。

📊 复杂度分析

  • 事件排序 O ( N log ⁡ N ) O(N \log N) O(NlogN)
  • 线段树更新 O ( N log ⁡ N ) O(N \log N) O(NlogN)
  • 总时间复杂度 O ( N log ⁡ N ) O(N \log N) O(NlogN)

✅ 适用场景

  • 计算多个矩形的面积并
  • 计算建筑投影面积
  • 计算不规则区间合并

📌 该方法适用于 N ≤ 10^5 的情况,效率极高!🚀


线段树的构建过程中,我们通常将一个区间 [l, r] 拆分,直到无法继续拆分为止。
在很多常见的线段树应用(如单点更新、区间查询)中,叶子节点往往是 l == r,但是在扫描线+线段树这种应用场景下,我们的离散化 y 坐标并不连续,因此叶子节点的定义有所不同l + 1 == r 才是叶子节点,而不是 l == r


🌟 为什么 l + 1 == r 才是叶子节点?

离散化的线段树是基于区间而不是单个点。

  • 常规线段树(连续区间)

    • 例如求区间最小值、区间和,通常是 l == r 作为叶子节点。
    • 但在这里,我们是计算“区间长度”,所以叶子节点应该是最小单位的区间 [y_coords[l], y_coords[r]],而不是单个点。
  • 离散化线段树(区间合并)

    • 叶子节点应该是最小的 y 轴区间,而 l == r 只表示单个坐标,不是一个“区间”。
    • l + 1 == r,意味着 y_coords[l]y_coords[r] 之间已经没有更多的离散坐标,可以认为它们形成了最小单位的“区间”,无法再继续细分,因此它是叶子节点。

🔹 详细举例

1️⃣ 假设 y_coords = {2, 5, 10, 15}

离散化后的 y_coords 索引:

y离散索引
20
51
102
153

表示的区间:

区间 [0,1] 表示 y = [2, 5]
区间 [1,2] 表示 y = [5, 10]
区间 [2,3] 表示 y = [10, 15]

2️⃣ 线段树的构造

构建线段树时,我们递归拆分区间 [0,3]

          [0,3]/     \[0,2]   [2,3]/     \[0,1]   [1,2]

在这棵树中:

  1. [0,1] 表示 [2,5],是叶子节点(因为 0+1 == 1
  2. [1,2] 表示 [5,10],是叶子节点(因为 1+1 == 2
  3. [2,3] 表示 [10,15],是叶子节点(因为 2+1 == 3

3️⃣ 为什么 l + 1 == r 作为叶子节点,而 l == r 不行?

如果 l == r 作为叶子节点,那就会出现 l = 0, r = 0 这种情况,这样的区间没有物理意义,因为:

  • y_coords[0] = 2 只是一个点,并不能表示一个范围。
  • y_coords[0]y_coords[1] ([2,5]) 形成了一个“区间”,才有意义。

所以,叶子节点的最小单位应该是 [y_coords[l], y_coords[r]],而不是单个点


✅ 结论

方式叶子节点定义适用情况是否适用于扫描线+线段树
l == r单个点适用于连续值(如 RMQ、区间和)❌ 不适用于离散区间
l + 1 == r一个离散区间适用于离散化的线段树(扫描线)✅ 适用

📌 代码示例

if (l + 1 == r) { // 叶子节点len[idx] = 0;  // 初始状态下没有被覆盖
} else { // 非叶子节点,合并左右子树len[idx] = len[idx * 2] + len[idx * 2 + 1];
}

🎯 总结

  • 线段树的叶子节点是最小单位的“区间”而不是单点,所以 l + 1 == r 作为叶子节点。
  • 常规线段树 l == r 适用于单点查询,但扫描线 + 线段树需要处理区间,所以 l + 1 == r 作为叶子节点
  • 这样,我们才能用 y_coords[r] - y_coords[l] 计算被覆盖的区间长度,否则 l == r 没有意义。

🚀 这样就能理解为什么 l + 1 == r 是叶子节点,而 l == r 不行了!

扫描线相关练习题目

  • 218. 天际线问题
  • P1904 天际线

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/76424.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java项目之基于ssm的简易版营业厅宽带系统(源码+文档)

项目简介 简易版营业厅宽带系统实现了以下功能&#xff1a; 此营业厅宽带系统利用当下成熟完善的SSM框架&#xff0c;使用跨平台的可开发大型商业网站的Java语言&#xff0c;以及最受欢迎的RDBMS应用软件之一的Mysql数据库进行程序开发。实现了营业厅宽带系统基础数据的管理&…

从入门到入土,SQLServer 2022慢查询问题总结

列为,由于公司原因,作者接触了一个SQLServer 2022作为数据存储到项目,可能是上一任的哥们儿离开的时候带有情绪,所以现在项目的主要问题就是,所有功能都实现了,但是就是慢,列表页3s打底,客户很生气,经过几周摸爬滚打,作以下总结,作为自己的成长记录。 一、索引问题…

PDF处理控件Aspose.PDF教程:在Python、Java 和 C# 中旋转 PDF 文档

您是否希望快速轻松地在线旋转PDF文档&#xff1f;无论您需要修复文档的方向还是只想重新排列页面&#xff0c;本指南都能满足您的需求。有简单的方法可以解决此问题 - 无论您喜欢在线工具还是编程解决方案。 在本指南中&#xff0c;我们将向您展示如何免费在线旋转 PDF&#…

编译原理:first集和follow

一、First 集&#xff08;首符号集&#xff09; 定义&#xff1a; 对于符号&#xff08;非终结符或终结符&#xff09;或符号串&#xff0c;First 集是该符号串能够推导出的所有可能开头的终结符的集合。若符号串可以推导出空串&#xff08;ε&#xff09;&#xff0c;则 ε 也…

python实现简单fast-cgi服务,对接到nginx

python代码 import socket import struct import threading# FastCGI 头格式&#xff08;8 字节&#xff09; FCGI_HEADER_FORMAT "!BBHHBx" FCGI_VERSION 1 FCGI_TYPE_BEGIN_REQUEST 1 FCGI_TYPE_PARAMS 4 FCGI_TYPE_STDIN 5 FCGI_TYPE_STDOUT 6 FCGI_TYPE_E…

vue开始时间小于等于结束时间,且开始时间小于等于系统时间,时间格式:年月日时分

// 日期配置 export const DATA_CONFIGS [{itemKey: "startDate",startDateKey: "startDate",endDateKey: "endDate",isStart: true,},{itemKey: "endDate",startDateKey: "startDate",endDateKey: "endDate",is…

PyCharm 下载与安装教程:从零开始搭建你的 Python 开发环境

PyCharm 是一款专为 Python 开发设计的集成开发环境&#xff08;IDE&#xff09;&#xff0c;它提供了强大的代码编辑、调试、版本控制等功能&#xff0c;是 Python 开发者的必备工具之一。如果你是初学者&#xff0c;或者正在寻找一款高效的开发工具&#xff0c;这篇文章将帮助…

Qt线程等待条件QWaitCondition

Qt 线程等待条件 概念 Qt提供了QWaitCondition类实现“等待条件”式的线程控制方法&#xff0c;它让线程阻塞在等待条件的地方&#xff0c;直到条件满足后才继续执行下去。也就是说&#xff0c;QWaitCondition可以使一个线程在满足一定条件时通知其他多个线程&#xff0c;使它…

RAG 和 RAGFlow 学习笔记

一、RAG&#xff08;检索增强生成&#xff09; 1. RAG 的定义与核心思想 RAG&#xff08;Retrieval-Augmented Generation&#xff0c;检索增强生成&#xff09; 是一种结合 信息检索&#xff08;Retrieval&#xff09; 和 文本生成&#xff08;Generation&#xff09; 的技术…

Windows连接服务器Ubuntu_MobaXterm

通过 SSH 远程连接&#xff08;命令行方式&#xff09; &#x1f527; 所需工具&#xff1a; Windows&#xff1a;MobaXterm&#xff08;强烈推荐&#xff09;或 PuTTY Ubuntu&#xff1a;已开启 SSH 服务 Ubuntu 开启 SSH 服务&#xff08;仅需一次&#xff09; 在 Ubuntu …

Rust 中的高效视频处理:利用硬件加速应对高分辨率视频

引言 在视频处理领域&#xff0c;随着4K、8K甚至更高分辨率内容的普及&#xff0c;传统的CPU计算方式逐渐显得力不从心。无论是视频剪辑、直播流处理还是格式转换&#xff0c;高负载场景下CPU占用过高的问题常常让开发者头疼。硬件加速技术通过利用GPU等专用硬件分担编解码任务…

大模型提示工程中,提示、补全、指令、上下文和样本这几个概念的区别是什么?

提示 (Prompt) 定义&#xff1a;输入给大模型的完整文本刺激&#xff0c;是与模型交互的主要方式。 特点&#xff1a; 是最广义的概念&#xff0c;包含其他几个元素整体输入的总和&#xff0c;包括指令、上下文和样本等内容决定模型如何理解和处理请求 示例&#xff1a; 分…

AI的未来演进

企业数字IP实战&#xff1a;创始人分身如何实现品宣获客双赢&#xff1f; ——从量子化建模到联邦学习的全链路技术拆解 一、行业痛点&#xff1a;品牌信任与获客效率的双重困局 2025年数据显示&#xff0c;73%的企业因传统营销模式效率低下错失市场机遇&#xff08;家居品牌…

软件定义无线电39

13.8 RFSoC上PYNQ的SDR设计流程 本节中详细介绍的设计过程可以分为六个独立的步骤&#xff0c;如图13.16所示&#xff0c;并在接下来的几页中进行讨论。 13.8.1 初始设计过程 。在这里&#xff0c;系统设计人员必须考虑许多因素&#xff0c;例如RFDC接收和/或发送的频率范围…

​自动化网络架构搜索(Neural Architecture Search,NAS)

NAS是一种旨在自动设计神经网络结构的技术。传统上&#xff0c;神经网络的架构设计依赖于专家的经验和大量的试错过程&#xff0c;而NAS通过算法自动搜索网络架构&#xff0c;以发现最适合特定任务的神经网络设计。 NAS的主要组成部分包括&#xff1a; 搜索空间&#xff1a;定…

Ubuntu 22.04 安装和运行 EDK2 超详细教程

Ubuntu 22.04 安装和运行 EDK2 超详细教程 适合新手小白&#xff0c;从零开始 &#x1f31f; 1. 什么是 EDK2&#xff1f; EDK2&#xff08;EFI Development Kit 2&#xff09;是一个开源的 UEFI&#xff08;统一可扩展固件接口&#xff09;开发环境&#xff0c;主要用于编写和…

什么是STEP认证

**什么是STEP认证** STEP认证&#xff0c;全称为“可持续纺织生产认证”&#xff08;Sustainable Textile Production&#xff09;&#xff0c;是一项由国际环保纺织协会Oeko-Tex提供的权威独立认证体系。这一认证体系犹如纺织和皮革行业的绿色灯塔&#xff0c;为追求可持续发…

odoo-045 ModuleNotFoundError: No module named ‘_sqlite3‘

文章目录 一、问题二、解决思路 一、问题 就是项目启动&#xff0c;本来好好地&#xff0c;忽然有一天报错&#xff0c;不知道什么原因。 背景&#xff1a; 我是在虚拟环境中使用的python3.7。 二、解决思路 虚拟环境和公共环境直接安装 sqlite3 都会报找不到这个库的问题…

[Linux系统编程]进程间通信—system V

进程间通信—system V 1. System V 共享内存(Shared Memory)1.1 共享内存的建立过程1.2 共享内存函数2. System V 消息队列(Message Queues)3. System V 信号量(Semaphores)4. 总结前言: 之前所提的管道通信是基于文件的,OS没有做过多的设计工作。 system V 进程间通信…

R语言——获取数据1

参考资料&#xff1a;学习R 数据的来源可以由很多。R内置有许多数据集&#xff0c;而在其他的附件包中能找到更多的数据。R能从各式各样的来源中读取&#xff0c;且支持大量的文件格式。 1、内置的数据集 R的基本分发包有一个datasets&#xff0c;里面全是示例数据集。很多其他…