掃描線離散化線段樹解決矩形面積并-洛谷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?<x2?109 0 ≤ y 1 < y 2 ≤ 10 9 0 \le y_1 < y_2 \le {10}^9 0y1?<y2?109

🚀 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.pswp.cn/diannao/76424.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/76424.shtml
英文地址,請注明出處:http://en.pswp.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;里面全是示例數據集。很多其他…