算法詳細講解:基礎算法 - 離散化/區間合并

離散化

講解

這里的離散化特指整數有序離散化。整個值域跨度很大,但是值非常稀疏的情況。

問題背景

我們有一個無限長的數軸,初始時每個位置上的值都是0。我們需要進行兩種操作:

  1. 修改操作:在某個位置?x?上增加一個值?c
  2. 查詢操作:詢問區間?[l, r]?內所有位置的值之和。

數據范圍

  • 操作次數?n?和查詢次數?m?最多為?10^5
  • 坐標?x?和區間端點?l,?r?的范圍是?-10^9?到?10^9
  • 修改值?c?的范圍是?-10000?到?10000

解題思路

由于坐標范圍非常大(-10^910^9),直接使用數組存儲每個位置的值會非常浪費空間。因此,我們需要使用離散化技術,將實際的坐標映射到一個小范圍內。

離散化步驟

  1. 收集所有需要離散化的值:包括所有的修改位置?x?和查詢區間的端點?l?和?r
  2. 排序并去重:對這些值進行排序,并去掉重復的值。
  3. 映射:將每個原始值映射到一個新的小范圍內的索引。

模板題

802. 區間和 - AcWing題庫

#include <bits/stdc++.h>
using namespace std;typedef pair<int, int> PII;
const int N = 300010;
int n, m;
int a[N], s[N]; // 存數數組與前綴和數組
vector<int> alls; // 存儲所有要離散化的值
vector<PII> add, query; // 添加與查詢// 二分查找找到 x 在 alls 中的位置
int find(int x) {int l = 0, r = alls.size() - 1;while (l < r) {int mid = l + r >> 1;if (alls[mid] >= x) r = mid;else l = mid + 1;}return r + 1; // 返回 x 映射后的位置
}int main() {cin >> n >> m;for (int i = 0; i < n; i++) {int x, c;cin >> x >> c;add.push_back({x, c});alls.push_back(x); // 收集需要離散化的值}for (int i = 0; i < m; i++) {int l, r;cin >> l >> r;query.push_back({l, r});alls.push_back(l);alls.push_back(r); // 收集需要離散化的值}// 對 alls 排序并去重sort(alls.begin(), alls.end());alls.erase(unique(alls.begin(), alls.end()), alls.end());// 處理插入操作for (auto item : add) {int x = find(item.first); // 找到 x 映射后的位置a[x] += item.second; // 在該位置增加 c}// 預處理前綴和for (int i = 1; i <= alls.size(); i++) s[i] = s[i - 1] + a[i];// 處理查詢操作for (auto item : query) {int l = find(item.first), r = find(item.second); // 找到 l 和 r 映射后的位置cout << s[r] - s[l - 1] << endl; // 輸出區間 [l, r] 的和}return 0;
}

模板題講解

pair 是為了 把“有關系的兩個數據”綁在一起,方便一起存儲和使用。

在本題中:

  • add?操作需要兩個數:位置?x?和?要加的值?c
  • query?查詢也需要兩個數:左端點?l?和?右端點?r

我們用 pair<int, int> 把它們“配對”起來,這樣就不會搞混。

想象你在記賬:

時間事件
3點在超市花 50 元
5點在餐廳花 100 元

你想記錄這些操作,你會怎么存?錯誤方式:

vector<int> times = {3, 5};
vector<int> money = {50, 100};

問題:如果排序或刪除,很容易“時間”和“金額”對不上!

vector<pair<int, int>> records = {{3, 50}, {5, 100}};

這樣,3點50元 永遠是一對,不會錯亂。

再回到題目:vector<PII> add;?—— 存修改操作

for (int i = 0; i < n; i++) {int x, c;cin >> x >> c;add.push_back({x, c});  // 把“位置x”和“加的值c”綁在一起
}

比如輸入:

1 2
3 6
7 5

add 變成:

add = { {1,2}, {3,6}, {7,5} }
  • {1,2}?表示:在位置 1 加 2
  • {3,6}?表示:在位置 3 加 6

好處:后面遍歷的時候,item.first 就是 xitem.second 就是 c。

find函數的作用是什么?

int find(int x) {int l = 0, r = alls.size() - 1;  // l=左邊界,r=右邊界while (l < r) {                  // 二分查找開始int mid = l + r >> 1;        // 相當于 (l + r) / 2,找中間位置if (alls[mid] >= x)          // 如果中間的數 >= xr = mid;                 // 說明 x 在左邊,把右邊界縮小到 midelse                         // 如果中間的數 < xl = mid + 1;             // 說明 x 在右邊,把左邊界移到 mid+1}return r + 1;  // 返回位置(+1 是因為窗口號從 1 開始,且r是下標,從0開始)
}

這個 find 函數,就是在一個排好序的列表里,快速找到某個數 x 的“排名”(位置),然后返回它的“新編號”。find(x) 就像查字典:給你一個詞(x),快速找到它在詞典(alls)里的頁碼(新編號),方便后面查數據。

舉個生活例子:排隊領號

想象你去銀行辦事:

  • 銀行有 10 個窗口,但今天只有 3 個人來辦業務,他們的號碼是:1, 3, 7
  • 銀行不想浪費窗口資源,于是說:“我們重新分配窗口號!”
  • 把這三個號碼?從小到大排序,然后按順序分配新窗口號:
    • 1?→ 窗口 1
    • 3?→ 窗口 2
    • 7?→ 窗口 3

這個過程就叫 離散化:把很大的原始號碼(比如 1億),映射到很小的窗口號(1,2,3)。

在你的代碼里,alls 就是那個“排好序的號碼列表”。比如:

alls = {1, 3, 7}  // 原始坐標,已經排序去重

我們想問:

“原始坐標 3 對應哪個窗口號?”

答案是:第 2 個位置 → 所以返回 2

原始坐標 x在 alls 中的下標返回值(r+1)新編號
100+1 = 11
311+1 = 22
722+1 = 33

為什么用二分?不用直接找?

因為 alls 可能有 30 萬個數,如果一個一個找(線性查找),太慢了!

  • 線性查找:最多要查 30 萬次
  • 二分查找:最多查?log?(30萬) ≈ 19?次

?快了上萬倍!

處理修改與查詢操作

第一部分:處理修改操作

for (int i = 0; i < n; i++) {int x, c;cin >> x >> c;           // 讀入位置 x 和要加的值 cadd.push_back({x, c});   // 把 (x, c) 存起來,后面要用alls.push_back(x);       // 把 x 記錄下來,準備離散化!
}
ixcadd?變成alls?變成
012{1,2}{1}
136{1,2},{3,6}{1,3}
275{1,2},{3,6},{7,5}{1,3,7}

所以現在:

  • add?存了所有“在哪加、加多少”
  • alls?存了所有被修改過的位置:1, 3, 7

第二部分:處理查詢操作

for (int i = 0; i < m; i++) {int l, r;cin >> l >> r;           // 讀入查詢區間 [l, r]query.push_back({l, r}); // 把 (l, r) 存起來,后面要用alls.push_back(l);       // 把 l 記下來,也要離散化!alls.push_back(r);       // 把 r 記下來,也要離散化!
}
ilrquery?變成alls?新增
013{1,3}1, 3?→?alls = {1,3,7,1,3}
146{1,3},{4,6}4,6?→?alls = {1,3,7,1,3,4,6}
278{1,3},{4,6},{7,8}7,8?→?alls = {1,3,7,1,3,4,6,7,8}

為什么查詢的?l?和?r?也要放進?alls

因為我們要對所有出現過的坐標進行離散化!

你想啊,后面要查 [4,6] 的和,但 46 這兩個位置根本沒人改過,它們不在 add 里。

但我們必須知道:

  • 4?在離散化后對應哪個編號?
  • 6?在離散化后對應哪個編號?

否則沒法查區間!

所以:只要是出現過的坐標(無論是修改還是查詢),都要放進 alls

后面代碼會做兩件事:

sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());

排序 + 去重后變成:

{1, 3, 4, 6, 7, 8}

然后我們就可以離散化了:

原始坐標新編號(find 返回)
11
32
43
64
75
86

處理插入,預處理前綴和,處理詢問(輸出每個區間的和)

第一段:處理插入(把值加到離散化后的位置)

for (auto item : add) {int x = find(item.first);        // 找到原始位置對應的新編號a[x] += item.second;             // 把值加到新位置上
}

第一次:item = {1,2}

  • item.first = 1?→?find(1) = 1?→?x = 1
  • a[1] += 2?→?a[1] = 2

第二次:item = {3,6}

  • find(3) = 2?→?x = 2
  • a[2] += 6?→?a[2] = 6

第三次:item = {7,5}

  • find(7) = 5?→?x = 5
  • a[5] += 5?→?a[5] = 5

現在 a 數組長這樣(只看 1~6):

新編號123456
值 a[i]260050

注意:a[i] 存的是離散化后編號為 i 的位置上的值

第二段:預處理前綴和

for (int i = 1; i <= alls.size(); i++)s[i] = s[i - 1] + a[i];

alls.size() = 6,所以 i 從 1 到 6

我們來算 s[i],它表示:前 i 個離散化位置的值的總和

i0123456
s[i]028881313

第三段:處理詢問(輸出每個區間的和)

for (auto item : query) {int l = find(item.first), r = find(item.second);cout << s[r] - s[l - 1] << endl;
}

舉個例子

查詢 1:item = {1,3}?→ 查?[1,3]?的和

  • l = find(1) = 1
  • r = find(3) = 2
  • 輸出:s[2] - s[1 - 1] = s[2] - s[0] = 8 - 0 = 8

區間合并

講解

區間合并就是對區間求并集。

給定一些區間,比如 [1,3][2,6],如果這些區間有交集(包括端點相交),我們就需要把它們合并成一個更大的區間。最終我們要計算合并后剩下多少個不重疊的區間。

我們可以看到:

  • [1, 2]?和?[2, 4]?有交集,可以合并成?[1, 4]
  • [7, 8]?和?[7, 9]?有交集,可以合并成?[7, 9]
  • [5, 6]?沒有和其他區間交集,保持不變

所以最終合并后的區間是:

  • [1, 4]
  • [5, 6]
  • [7, 9]

因此,合并后的區間個數是 3

模板題

AcWing 803. 區間合并 - AcWing

#include <bits/stdc++.h>
using namespace std;typedef pair<int, int> PII;// 合并區間的函數
void merge(vector<PII> &segs) {// 結果數組,存儲合并后的區間vector<PII> res;// 對輸入的區間按照起始位置進行排序sort(segs.begin(), segs.end());// 初始化當前區間的起始和結束位置int st = -2e9, ed = -2e9;// 遍歷所有區間for (auto seg : segs) {// 如果當前區間的起始位置大于上一個區間的結束位置if (ed < seg.first) {// 如果st不是初始值,說明之前已經有一個區間了,將其加入結果數組if (st != -2e9) {res.push_back({st, ed});}// 更新當前區間的起始和結束位置st = seg.first, ed = seg.second;} else {// 如果當前區間的起始位置小于等于上一個區間的結束位置,說明有交集// 更新當前區間的結束位置為兩者中的較大值ed = max(ed, seg.second);}}// 最后檢查是否有剩余的區間未加入結果數組if (st != -2e9) {res.push_back({st, ed});}// 將結果數組賦值給原數組,完成合并操作segs = res;
}int main() {// 區間數量int n;cin >> n;// 創建一個vector來存儲所有的區間vector<PII> segs;// 讀取每個區間并存入vector中for (int i = 0; i < n; i++) {int l, r;cin >> l >> r;segs.push_back({l, r});}// 調用merge函數進行區間合并merge(segs);// 輸出合并后的區間個數cout << segs.size() << endl;return 0;
}

模板題講解

int st = -2e9, ed = -2e9; 這一步是怎么來的?

目的是初始化一個“虛擬”的起始區間,用來方便地處理第一個真實區間的合并邏輯。

注意:-2e9 就是 -2 * 10^9,題目說區間范圍是 -10^9 ≤ li ≤ ri ≤ 10^9,所以 -2e9 比所有可能的左端點都小,可以當作“負無窮”。

int st = -2e9, ed = -2e9; // 初始狀態:沒有正在合并的區間
for (auto seg : segs) {if (ed < seg.first) {  // 當前區間無法和 seg 合并(斷開了)if (st != -2e9) {  // 如果 st 不是初始值,說明我們之前已經在合并某個真實區間res.push_back({st, ed}); // 把之前合并好的區間存進去}// 開啟一個新的合并區間:就是當前這個 segst = seg.first;ed = seg.second;} else {// 能合并!更新當前合并區間的右邊界ed = max(ed, seg.second);}
}
// 循環結束后,最后一個正在合并的區間還沒加入結果
if (st != -2e9) {res.push_back({st, ed});
}

為什么需要這個“虛擬起點”?

是為了統一處理邏輯,避免寫一堆 if (第一個區間) 的判斷。

比如如果沒有這個技巧,你可能會寫:

if (res.empty()) {st = seg.first;ed = seg.second;
} else if (ed < seg.first) {res.push_back({st, ed});st = seg.first;ed = seg.second;
} else {ed = max(ed, seg.second);
}

int st = -2e9, ed = -2e9; 是一種編程技巧,叫做“哨兵”或“虛擬初始值”,作用是:

  • 讓你在處理第一個真實區間時,也能套用統一的邏輯;
  • 用?st != -2e9?來判斷“是否已經開始合并一個真實區間”;
  • 避免特判第一個元素,讓代碼更簡潔、不易出錯。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/93300.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/93300.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/93300.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SpringBoot 實現在線查看內存對象拓撲圖 —— 給 JVM 裝上“透視眼”

0. 你將獲得什么 一個可嵌入任何 Spring Boot 應用的內存對象拓撲服務&#xff1a;訪問 /memviz.html 就能在瀏覽器看見對象圖。 支持按類/包名過濾、按對象大小高亮、點擊節點看詳情。 線上可用&#xff1a;默認只在你點擊“生成快照”時才工作&#xff1b;日常零開銷。 1.…

STM32 HAL驅動MPU6050傳感器

STM32 HAL驅動MPU6050傳感器 項目概述 本項目實現了基于STM32 HAL庫的MPU6050傳感器驅動&#xff0c;可以讀取加速度計和陀螺儀數據。項目使用I2C接口與MPU6050通信&#xff0c;并通過UART接口輸出數據。 項目倉庫地址&#xff1a;STM32_Sensor_Drives 硬件連接 MPU6050 I2…

flex-wrap子元素是否換行

flex-wrap設置子元素是否換行&#xff0c;默認情況下&#xff0c;項目都排在一條線&#xff08;又稱”軸線”&#xff09;上。flex-wrap屬性定義&#xff0c;flex布局中默認是不換行的。1、div的寬度是600px&#xff0c;每個span的寬度是150px&#xff0c;總共有5個&#xff0c…

RabbitMQ面試精講 Day 21:Spring AMQP核心組件詳解

【RabbitMQ面試精講 Day 21】Spring AMQP核心組件詳解 開篇 歡迎來到"RabbitMQ面試精講"系列第21天&#xff01;今天我們將深入探討Spring AMQP的核心組件&#xff0c;這是Java開發者集成RabbitMQ最常用的框架。掌握Spring AMQP不僅能提升開發效率&#xff0c;更是…

Flink TableAPI 按分鐘統計數據量

一、環境版本環境版本Flink1.17.0Kafka2.12MySQL5.7.33二、MySQL建表腳本 create table user_log (id int auto_increment comment 主鍵primary key,uid int not null comment 用戶id,event int not null comment 用戶行為,logtime bigint null comment 日志時…

18.13 《3倍效率提升!Hugging Face datasets.map高級技巧實戰指南》

3倍效率提升!Hugging Face datasets.map高級技巧實戰指南 實戰項目:使用 datasets.map 進行高級數據處理 在大模型訓練過程中,數據預處理的質量直接決定了模型最終的表現。Hugging Face Datasets 庫提供的 datasets.map 方法是處理復雜數據場景的瑞士軍刀,本章將深入解析…

實體店獲客新引擎:數據大集網如何破解傳統門店引流難題

在商業競爭日益激烈的當下&#xff0c;實體店的生存與發展正面臨前所未有的挑戰。無論是街邊的小型便利店&#xff0c;還是大型購物中心的連鎖品牌&#xff0c;都在為"如何吸引顧客進店"而絞盡腦汁。傳統廣告投放效果不佳、線下流量持續萎縮、客戶轉化率難以提升………

LeetCode 分類刷題:2302. 統計得分小于 K 的子數組數目

題目一個數組的 分數 定義為數組之和 乘以 數組的長度。比方說&#xff0c;[1, 2, 3, 4, 5] 的分數為 (1 2 3 4 5) * 5 75 。給你一個正整數數組 nums 和一個整數 k &#xff0c;請你返回 nums 中分數 嚴格小于 k 的 非空整數子數組數目。子數組 是數組中的一個連續元素序…

TDengine IDMP 基本功能(1.界面布局和操作)

UI 布局和操作說明 TDengine IDMP 的用戶界面&#xff08;UI&#xff09;設計旨在提供直觀、易用的操作體驗。下面介紹 UI 的主要區域和典型操作&#xff1a; 主要區域 IDMP 的用戶界面是完全基于瀏覽器的。登錄后的典型 UI 界面具有幾個區域&#xff1a; 主菜單&#xff1a;AI…

QT(概述、基礎函數、界面類、信號和槽)

一、概述1、QTQT是一個c的第三方庫&#xff0c;是專門用來進行界面編程的一個庫 1. QT本身實現了多種軟件&#xff1a; 2. ubuntu系統中所有界面都是QT做的 3. 最新版本的QQ也是QT做的 4. 嵌入式編程中&#xff0c;幾乎所有的上位機&#xff0c;都可以使用QT來做 QT本身除了實現…

【從零開始java學習|第六篇】運算符的使用與注意事項

目錄 一、算術運算符 1. 基本算術運算符&#xff08;二元&#xff09; 2. 自增 / 自減運算符&#xff08;一元&#xff09; 二、類型轉換&#xff08;隱式與強制&#xff09; 1. 隱式轉換&#xff08;自動類型轉換&#xff09; ?編輯 2. 強制轉換&#xff08;顯式類型轉…

shellgpt

一、介紹 官網&#xff1a;https://github.com/TheR1D/shell_gpt ShellGPT&#xff08;shell_gpt&#xff09; 是一款把 GPT 系列大模型能力直接搬到終端 的開源命令行生產力工具。用日常英語或中文描述需求&#xff0c;就能幫你 生成、解釋甚至自動執行 Shell 命令&#xff…

geoserver sql視圖調用Postgis自定義函數問題記錄

一、問題描述&#xff1a;geoserver sql視圖調用Postgis自定義函數對點圖層增加一條記錄時&#xff0c;返回結果主鍵自增ID加了2&#xff0c;但表中數據只增加一條記錄。 但在pgAdmin中直接寫SQL調用Postgis自定義函數對點圖層增加一條記錄時&#xff0c;返回結果主鍵自增ID只加…

#T1224. 最大子矩陣

題目傳送 題目描述 已知矩陣的大小定義為矩陣中所有元素的和。給定一個矩陣&#xff0c;你的任務是找到最大的非空(大小至少是11)子矩陣。 比如&#xff0c;如下44的矩陣 0 -2 -7 09 2 -6 2 -4 1 -4 1-1 8 0 -2的最大子矩陣是 9 2-4 1-1 8這…

2025年大模型安全崗的面試匯總(題目+回答)

安全領域各種資源&#xff0c;學習文檔&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各種好玩的項目及好用的工具&#xff0c;歡迎關注。 目錄 1. Transformer核心機制及其對LLM突破的基石作用 2. LLM能力邊界評估框架設計 3. 模型層級安全風險分析 …

《關于省級政務云服務費支出預算標準的規定》豫財預〔2024〕106號解讀

《關于省級政務云服務費支出預算標準的規定》豫財預〔2024〕106號文件由河南省財政廳編制經省政府同意后于2024年12月3日印發執行&#xff0c;規定作為省級政務云服務費支出預算編制和審核的依據&#xff0c;旨在加強省級部門預算管理&#xff0c;規范政務云服務費支出預算編制…

使用HalconDotNet實現異步多相機采集與實時處理

文章目錄 一、核心功能與原理 功能目標: 工作原理: 關鍵機制: 二、完整C#實現代碼 三、關鍵實現解析 1. 零拷貝圖像傳輸 2. 動態幀率控制 3. HALCON并行優化 4. 異常隔離機制 四、高級優化策略 1. 硬件加速配置 2. 內存池管理 3. 實時性保障 一、核心功能與原理 功能目標:…

《瘋狂Java講義(第3版)》學習筆記ch4

ch4流程控制與數組1.switch語句后的expression表達式的數據類型只能是byte、short、char、int四種證書類型。2.建議不要在循環體內修改循環變量&#xff08;也叫循環計數器&#xff09;的值&#xff0c;否則會增加程序出錯的可能性。3.定義數組推薦語法格式&#xff1a;type[] …

COLMAP進行密集重建,三維重建的步驟

密集重建是在稀疏重建的基礎上進行的 稀疏重建見&#xff1a;用 COLMAP GUI 在 Windows 下一步步完成 相機位姿估計&#xff08;SfM&#xff09; 和 稀疏點云重建的詳細步驟&#xff1a;_colmap database導入圖片位姿-CSDN博客 完成稀疏重建后直接進入以下步驟進行密集重建&am…

基于飛算JavaAI實現Reactor模式服務器的深度實踐

一、飛算JavaAI技術概述 1.1 飛算JavaAI平臺簡介飛算JavaAI是飛算科技推出的智能化Java開發平臺&#xff0c;通過AI技術賦能傳統軟件開發流程&#xff0c;為開發者提供從需求分析到代碼實現的全流程智能化解決方案。該平臺深度融合了人工智能技術與軟件開發實踐&#xff0c;具備…