線段樹詳解【數據結構】

簡介

線段樹是一種應用極其廣泛,使用范圍較廣并且非常知名的樹形數據結構,主要用于進行區間操作,如區間修改,區間查詢等。這種數據結構唯一的不足就是巨大的代碼量,因此處理一些較簡單的問題時建議用樹狀數組。

原理

其實線段樹的原理是比較簡單的。我們平常見到的樹都是沒有特殊含義的,而線段樹的每個點都表示一個區間。詳細來講,線段樹其實是一顆二叉樹,我們會把一個數組轉換成一顆這樣的樹并進行多種操作。這是一顆線段樹的大致樣子。

可以看見,父節點表示的區間是兩個子節點表示的區間拼出來的。但是如果不知道父節點或子節點是誰這顆樹維護不了,我們因此可以定義一個結點u,并設它的左兒子為u*2,右兒子為u*2+1。這樣子就不可能存在重復結點的情況。當然這樣會消耗大量空間,因此要用到優化,我們待會兒講。

可以看出來如果想表示區間中的一段只需把這些分散的區間組合起來。那我們應該怎么組合?我們假設查詢的區間為[l,r],如果我們檢測到一個區間被完全包含我們就返回它的值。否則,我們取該區間的中間,把這個區間割開。如果檢測到查詢區間有一部分在左邊的區間,我們去查左邊。查右邊同理。這樣,我們可以以logn的復雜度把序列多余的部分一點一點消掉,直到無法查詢為止。

舉個例子,我們現在要在上圖中查詢[1,3]的值。首先我們從結點1開始,這里沒有完全包含,我們把結點1的區間從中間切開,變為[0,3]和[4,7],可以看出[4,7]與查詢區間完全不包含,我們轉而搜索[0,3]。

因為[0,3]依舊不是完全包含,我們將它切開,這是[2,3]被確定為完全包含,我們記錄[2,3]的值,發現[0,1]里只有1包含,我們把它提出來,這樣我們就把[1,3]拆成了1和[2,3]。

這時我們就完成了區間查詢,但我們可能還需要進行區間修改。怎么做呢?我們只需讓每個結點多維護一個懶標記。這個東西非常重要,因為如果每次我們都要把所有修改區間的子區間修改一遍復雜度不如直接修改。我們發現我們如果已經像之前把修改區間拆開,它們下面的結點目前沒有必要改,我們完全可以等搜到子節點以后再改。這樣修改也變成了logn的。

具體怎么操作?我們假設進行加操作,每次我們把修改的區間的懶標記加上修改值,等到有需要訪問子節點的操作時,我們把子節點的和加上其區間長度乘懶標記的值,并把懶標記加給左右兒子,最后把自己的懶標記清空就可以了。

實現

建樹

struct tree{int l,r,sum,lazy;
}t[4*N];
void build(int u, int l, int r){t[u].l=l,t[u].r=r;if(l==r){t[u].sum=a[l];return;}int mid=(l+r)>>1;build(u*2,l,mid), build(u*2+1,mid+1,r);t[u].sum=t[u*2].sum+t[u*2+1].sum;
}

這里的u就是每個樹上結點的編號,l和r表示這個結點表示的區間。當l=r說明這個結點表示一個元素,直接賦值即可。在搜索完子節點我們更新父節點的和。

懶標記下傳

void pushdown(int u){if(t[u].lazy){t[u*2].sum+=t[u].lazy*(t[u*2].r-t[u*2].l+1);t[u*2+1].sum+=t[u].lazy*(t[u*2+1].r-t[u*2+1].l+1);t[u*2].lazy+=t[u].lazy, t[u*2+1].lazy+=t[u].lazy;t[u].lazy=0;		}
}

這里的lazy就是懶標記,這里我們把父節點的懶標記加給兩個子節點,同時維護原來就該加的值。

區間修改

void update(int u, int l, int r, int c){if(t[u].l>=l&&t[u].r<=r){t[u].sum+=c*(t[u].r-t[u].l+1);t[u].lazy+=c;return;}pushdown(u);int mid=(t[u].l+t[u].r)>>1;if(mid>=l) update(u*2,l,r,c);if(r>mid) update(u*2+1,l,r,c);t[u].sum=t[u*2].sum+t[u*2+1].sum;
}

這里的l,r,c都是區間加的參數,不會變,真正表示結點信息的是u。我們看上面的判斷表示如果這個結點表示的區間被完全包含我們就給它帶上懶標記,維護區間和。如果不完全包含我們就需要下傳懶標記讓每次查詢時獲得的都是正確的值。這里我們去中間值將區間拆開。r需要大于mid是因為第二個區間的左端點是mid+1,我們需要判斷一下,這里寫r>=mid+1也是可以的。

區間查詢

int find(int u, int l, int r){if(t[u].l>=l&&t[u].r<=r) return t[u].sum;pushdown(u);int ans=0, mid=(t[u].l+t[u].r)>>1;if(mid>=l) ans+=find(u*2,l,r);if(r>mid) ans+=find(u*2+1,l,r);return ans;
}

這里的l,r同樣是查詢區間的左右端點,不會改變。可以看到我們每次查詢兒子前都要下傳懶標記,這樣兒子的值才是對的。我們只需維護一個計數器一層一層把答案傳上來即可。

優化

假設一個序列特別長,有一千萬個元素。這個時候開4倍空間會爆炸。這時我們就要用到動態開點。我們怎么做?之前定義了u的左兒子是u*2,右兒子是u*2+1,但這樣可能浪費大量空間。我們因此取消這個定義,維護一個計數器,表示目前有多少點。我們存兩個變量表示一個結點的兒子,每次添加時我們讓這個變量變成計數器,然后計數器++。

這樣我們可以節省大量空間,但代碼也極度復雜,這里就不放了。推薦一下OIwiki的模版。

例題及完整代碼

區間加區間查模版

代碼:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,m,a[N],op,x,y,k;
struct tree{int l,r,sum,lazy;
}t[4*N];
void build(int u, int l, int r){t[u].l=l,t[u].r=r;if(l==r){t[u].sum=a[l];return;}int mid=(l+r)>>1;build(u*2,l,mid), build(u*2+1,mid+1,r);t[u].sum=t[u*2].sum+t[u*2+1].sum;
}
void pushdown(int u){if(t[u].lazy){t[u*2].sum+=t[u].lazy*(t[u*2].r-t[u*2].l+1);t[u*2+1].sum+=t[u].lazy*(t[u*2+1].r-t[u*2+1].l+1);t[u*2].lazy+=t[u].lazy, t[u*2+1].lazy+=t[u].lazy;t[u].lazy=0;		}
}
void update(int u, int l, int r, int c){if(t[u].l>=l&&t[u].r<=r){t[u].sum+=c*(t[u].r-t[u].l+1);t[u].lazy+=c;return;}pushdown(u);int mid=(t[u].l+t[u].r)>>1;if(mid>=l) update(u*2,l,r,c);if(r>mid) update(u*2+1,l,r,c);t[u].sum=t[u*2].sum+t[u*2+1].sum;
}
int find(int u, int l, int r){if(t[u].l>=l&&t[u].r<=r) return t[u].sum;pushdown(u);int ans=0, mid=(t[u].l+t[u].r)>>1;if(mid>=l) ans+=find(u*2,l,r);if(r>mid) ans+=find(u*2+1,l,r);return ans;
}
signed main(){cin>>n>>m;for(int i=1;i<=n;i++) cin>>a[i];build(1,1,n);while(m--){cin>>op;if(op==1){cin>>x>>y>>k;update(1,x,y,k);}else{cin>>x>>y;cout<<find(1,x,y)<<endl;}}return 0;
}

線段樹進階題目

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,q,mod,a[N],op,x,y,k;
struct tree{int l,r,sum,mul,add;
}t[4*N];
void build(int u, int l, int r){t[u].l=l,t[u].r=r,t[u].mul=1;if(l==r){t[u].sum=a[l]%mod;return;}int mid=(l+r)>>1;build(u*2,l,mid);build(u*2+1,mid+1,r);t[u].sum=t[u*2].sum+t[u*2+1].sum;t[u].sum%=mod;
}
void pushd(int u){if(t[u].mul!=1||t[u].add){int L=u*2,R=u*2+1;t[L].sum=(1ll*t[L].sum*t[u].mul+t[u].add*(t[L].r-t[L].l+1))%mod;t[R].sum=(1ll*t[R].sum*t[u].mul+t[u].add*(t[R].r-t[R].l+1))%mod;t[L].mul=(1ll*t[L].mul*t[u].mul)%mod;t[R].mul=(1ll*t[R].mul*t[u].mul)%mod;t[L].add=(1ll*t[L].add*t[u].mul+t[u].add)%mod;t[R].add=(1ll*t[R].add*t[u].mul+t[u].add)%mod;t[u].mul=1,t[u].add=0;}
}
void update1(int u, int l, int r, int k){if(t[u].l>=l&&t[u].r<=r){t[u].sum=(1ll*t[u].sum*k)%mod;t[u].mul=(1ll*t[u].mul*k)%mod;t[u].add=(1ll*t[u].add*k)%mod;return;}pushd(u);int mid=(t[u].l+t[u].r)>>1;if(l<=mid) update1(u*2,l,r,k);if(r>mid) update1(u*2+1,l,r,k);t[u].sum=(t[u*2].sum+t[u*2+1].sum)%mod;
}
void update2(int u, int l, int r, int k){if(t[u].l>=l&&t[u].r<=r){t[u].sum=(t[u].sum+1ll*k*(t[u].r-t[u].l+1))%mod;t[u].add=(t[u].add+k)%mod;return;}pushd(u);int mid=(t[u].l+t[u].r)>>1;if(l<=mid) update2(u*2,l,r,k);if(r>mid) update2(u*2+1,l,r,k);t[u].sum=(t[u*2].sum+t[u*2+1].sum)%mod;
}
int find(int u, int l, int r){if(t[u].l>=l&&t[u].r<=r) return t[u].sum;pushd(u);int mid=(t[u].l+t[u].r)>>1,ans=0;if(l<=mid) ans+=find(u*2,l,r);if(r>mid) ans+=find(u*2+1,l,r);return ans;
}
signed main(){cin>>n>>q>>mod;for(int i=1;i<=n;i++) cin>>a[i];build(1,1,n);while(q--){cin>>op;if(op==1){cin>>x>>y>>k;update1(1,x,y,k);}else if(op==2){cin>>x>>y>>k;update2(1,x,y,k);}else{cin>>x>>y;cout<<find(1,x,y)%mod<<endl;}}return 0;
}

這里再給大家推薦幾個題單。

適合入門的題單

適合進階的題單

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

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

相關文章

Maven 入門與進階:聚合、繼承與生命周期詳解

Maven 是 Java 項目管理的核心工具&#xff0c;其強大的依賴管理、項目構建和模塊化設計能力&#xff0c;極大地提升了開發效率。本文將深入探討 Maven 的 聚合&#xff08;Multi-module&#xff09;、繼承&#xff08;Inheritance&#xff09; 和 生命周期&#xff08;Lifecyc…

手搓MCP客戶端動態調用多MCP服務,調用哪個你說了算!

01 引言 前兩天&#xff0c;有個粉絲朋友咨詢MCP服務如何動態調用&#xff0c;動態加載MCP服務的鏈接&#xff1f;我們都知道MCP客戶端可以配置多個MCP服務的地址&#xff1a; spring.ai.mcp.client.sse.connections.server1.urlhttp://localhost:xxxx spring.ai.mcp.client.ss…

Go語言中的優雅并發控制:通道信號量模式詳解

在Go語言的并發編程中&#xff0c;“通過通信共享內存”的設計哲學貫穿始終。當面對高并發場景時&#xff0c;無限制創建goroutine可能導致資源耗盡、CPU過載等問題&#xff0c;通道信號量模式&#xff08;Channel Semaphore Pattern&#xff09; 正是一種基于Go通道特性的優雅…

鴻蒙 NEXT開發中輕松實現人臉識別功能

大家好&#xff0c;我是 V 哥。 今天給大家介紹在 HarmonyOS 原生鴻蒙開發中&#xff0c;實現人臉識別功能&#xff0c;這個功能在常用的 APP 開發中上鏡率還是很高的&#xff0c;在傳統的 Android 或 iOS 開發中&#xff0c;通常我們要借助第三方庫來實現&#xff0c;而在鴻蒙…

華為開發者空間訓練營-優秀作品公布

排名標題總分獎品1手把手教你開發一個地區智能查詢MCP&#xff0c;賦能地理位置類MCP服務的“零輸入”無感交互95華為 freebuds 6i 藍牙耳機2基于華為開發者空間云主機DeepSeek助力電商企業AI海報文案驅動的最佳實踐落地 94華為 freebuds 6i 藍牙耳機32小時基于華為開發者空間和…

基于Python與Tkinter開發的微博多功能自動化助手

文章目錄 摘要 1. 背景與意義 2. 需求分析 3. 核心架構設計 3.1. 技術選型 3.2. 核心思想:UI與邏輯分離的異步架構 4. 深度模塊化剖析 4.1. 微博核心API交互模塊 4.2. 健壯性設計:代理與重試機制 4.3. GUI界面模塊 (WeiboApp 類) 4.4. 異步任務處理模塊 5. 難點分析與解決方案…

效果驅動復購!健永科技RFID牛場智能稱重項目落地

近日&#xff0c;北京某養殖企業持續下單電子耳標識讀器&#xff0c;在牛場智能稱重中落地應用&#xff0c;通過自動、準確地識別牛只并記錄體重數據&#xff0c;顯著提升效率和數據精準度&#xff0c;實現了“效果驅動復購”的良性循環。健永科技RFID技術在北京某養殖企業智能…

計算機網絡:2、TCP和UDP

2、TCP和UDP 簡介 TCP(transmission Control Protocol)&#xff1a;是一種通信標準&#xff0c;它使應用程序和計算設備能夠在網絡上交換消息。它的設計目的是在互聯網上發送數據包&#xff0c;并確保數據和信息在網絡上的成功傳遞。UDP(the User Datagram Protocol)&#xf…

WEB安全篇:瀏覽器攻擊原理及防護

1、XSS&#xff1a;跨站腳本攻擊就是攻擊者想盡一切辦法將可以執行的代碼注入到網頁中。攻擊者在web頁面惡意插入HTML或script標簽&#xff0c;當用戶瀏覽該頁面時&#xff0c;惡意代碼就會被執行&#xff0c;從而達到攻擊的目的。XSS利用的是用戶對指定網站的信任。比如&#…

匯編語言學習2---GNU Debugger (GDB)

學習記錄&#xff0c;在匯編語言 &#xff0c;我們面對的是機器碼&#xff08;以匯編指令形式展現&#xff09;&#xff0c;所以斷點要設置在機器碼被加載到內存中的位置。 GEF插件使用 安裝插件wget -O ~/.gdbinit-gef.py -q https://gef.blah.cat/pyecho source ~/.gdbinit-g…

談談架構的內容

一、架構的定義架構是一個界定不清的東西&#xff0c;我們很難講清楚哪些東西是架構&#xff0c;哪些東西不是架構。但軟件行業里其實人人都在搞架構&#xff0c;軟件設計就是架構本身。架構這個詞出現得很早&#xff0c;有些人認為是 NASA&#xff08;也可能是NATO&#xff09…

C#文件(夾)讀取相關(完善中。。。)

前言閱讀項目編輯器的代碼時&#xff0c;發現好多與文件&#xff08;夾&#xff09;路徑相關代碼。本來自己之前對路徑相關的東西就模模糊糊&#xff0c;希望通過這篇筆記能讓自己模糊的地方明朗一下。" / " 與 " \ "你是否有過這樣的疑惑&#xff1a;Wind…

FPGA DP1.4 With DSC解決方案

引言&#xff1a;迎接高清高刷時代的顯示挑戰隨著8K分辨率、高刷新率、HDR和更廣色域內容的普及&#xff0c;傳統視頻接口的帶寬正面臨極限。DisplayPort 1.4標準雖提供了高達32.4 Gbps的帶寬&#xff08;HBR3速率&#xff09;&#xff0c;但要無壓縮地傳輸8K60Hz 10bpp HDR視頻…

新手向:Python開發簡易網絡服務器

Python網絡服務器開發指南&#xff1a;從零開始的完整實現網絡服務器基礎概念網絡服務器是互聯網基礎設施的核心組件&#xff0c;它本質上是一個持續運行的程序&#xff0c;負責監聽特定端口&#xff08;如HTTP服務的80端口或HTTPS的443端口&#xff09;&#xff0c;處理來自客…

819 機器學習-決策樹2

一、決策樹的算法信息增益&#xff1a;某個屬性帶來的熵增1、決策樹三大經典算法? ID3 → 信息增益 信息增益&#xff1a;某個屬性帶來的熵增? C4.5 → 信息增益率 信息增益率&#xff1a;信息增益自身熵? CART → 基尼指數&#xff08;分類&#xff09;&#xff1b;平方誤…

Objective-C 版本的 LiveEventBus 效果

想要 Objective-C 版本的 LiveEventBus 效果&#xff08;跨頁面/跨模塊通信&#xff0c;支持粘性和非粘性事件&#xff09;。在 iOS 里對應的就是 NSNotificationCenter&#xff0c;但是它 默認不支持粘性事件&#xff0c;所以如果你想要“粘性”&#xff0c;需要自己封裝一層。…

WindowsAPI|每天了解幾個winAPI接口之網絡配置相關文檔Iphlpapi.h詳細分析七

上一篇&#xff1a;WindowsAPI|每天了解幾個winAPI接口之網絡配置相關文檔Iphlpapi.h詳細分析六 如果有錯誤歡迎指正批評&#xff0c;在此只作為科普和參考。 C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\um\iphlpapi.h 文章目錄CreateIpNetEntry&#xff1…

STM32F407VGT6從零建立一個標準庫工程模板+VSCode或Keil5

一、前言 下載平臺:STM32F407ZGT6 代碼使用平臺:VSCode 編譯器:arm-none-aebi-gcc ---- 默認你已經安裝 程序下載工具:STlink ---- 默認你擁有 批處理工具:make ---- 默認你已經安裝 使用此方法可以不借助其它插件&#xff0c;例如:STM32EIDE。這個方法已經經過驗證可以在STM3…

佩京VR黨建工作站-黨建VR系統-VR黨建展廳

VR黨建工作站是一種依托VR虛擬現實技術的數字化黨建文化學習工具。它通過將豐富的學習內容植入到智慧黨建科技產品中&#xff0c;構建出沉浸式的學習場景&#xff0c;從而創新了體驗式學習模式&#xff0c;促進了黨員的自主學習。VR黨建工作站核心功能&#xff1a;1、了解實時新…

Kotlin 協程之Channel的概念和基本使用

前言 在 專欄 之前的文章中&#xff0c;我們已經知道了協程的啟動、掛起、取消、異常以及常用的協程作用域等基礎應用。 這些基礎應用適合的場景是一次性任務&#xff0c;執行完就結束了的場景。 launch / async 適合的場景 網絡請求數據庫查詢文件讀寫并行計算任務等等 而…