線段樹模板

2022-05-10 23:03:20 字數 4871 閱讀 8759

ps: _此處以詢問區間和為例。實際上線段樹可以處理很多符合結合律的操作。

(比如說加法,a[1]+a[2]+a[3]+a[4]=(a[1]+a[2])+(a[3]+a[4]))

線段樹之所以稱為「樹」,是因為其具有樹的結構特性。

線段樹由於本身是專門用來處理區間問題的(包括rmq、rsq問題等)。

(**於網際網路。

對於每乙個子節點而言,都表示整個序列中的一段子區間;

對於每個葉子節點而言,都表示序列中的單個元素資訊;

子節點不斷向自己的父親節點傳遞資訊,而父節點儲存的資訊則是他的每乙個子節點資訊的整合。

有沒有覺得很熟悉?

對,線段樹就是分塊思想的樹化,或者說是對於資訊處理的二進位製化

用於達到o(logn)級別的處理速度,log以2為底。

(其實以幾為底都只不過是個常數,可忽略)。

而分塊的思想,則是可以用一句話總結為:

通過將整個序列分為有窮個小塊,

對於要查詢的一段區間,總是可以整合成k個所分塊與m個單個元素的資訊的並集(0<=k,m<=sqrt(n))。

但普通的分塊不能高效率地解決很多問題,所以作為log級別的資料結構,線段樹應運而生。

extratips:

其實,雖然線段樹的時間效率要高於分塊

但是實際上分塊的總合併次數不會超過sqrt(n)​

但是線段樹在最壞情況下的合併次數

顯然是要大於這個時間效率的qwq。

但是畢竟也只是乙個很大的常數而已

雖說如此,分塊的應用範圍還是要廣於線段樹的,

因為雖然線段樹好像很快,但是它只能維護帶有結合律的資訊,

比如區間max/min、sum、xor之類的,

但是不帶有結合律的資訊就不能維護(且看下文分解);

而分塊則靈活得多,可以維護很多別的東西,

因為實際上分塊的本質就是優雅的暴力。

其實越暴力的演算法可以支援的操作就越多、功能性就越強.

n^2n2的暴力幾乎什麼都可以維護(huaji

由於二叉樹的自身特性,對於每個父親節點的編號ii,他的兩個兒子的編號分別是2i2i和2i+12i+1,所以我們考慮寫兩個o(1)o(1)的取兒子函式:

int n;

int ans[maxn*4];

inline int ls(int p)//左兒子

inline int rs(int p)//右兒子

extratips:

1、此處的inlineinline可以有效防止無需入棧的資訊入棧,節省時間和空間。

2、二進位制位左移一位代表著數值*2,而如果左移完之後再或上1,由於左移完之後最後一位二進位制位上一定會是0,所以|1等價於+1。

用二進位制運算不是為了裝x,相信我,會快的!

那麼根據線段樹的服務物件,可以得到線段樹的維護:

void push_up_sum(int p)// 向上不斷維護區間操作 

void push_up_min(int p)

此處一定要注意,push_up操作的目的是為了維護父子節點之間的邏輯關係。

當我們遞迴建樹時,對於每乙個節點我們都需要遍歷一遍,

並且電腦中的遞迴實際意義是先向底層遞迴,然後從底層向上回溯,

所以開始遞迴之後必然是先去整合子節點的資訊,

再向它們的祖先回溯整合之後的資訊。

(其實是正確性證明)

實際上push_up是在合併兩個子節點的資訊,所以需要資訊滿足結合律!

那麼對於建樹,由於二叉樹自身的父子節點之間的可傳遞關係,所以可以考慮遞迴建樹,

並且在建樹的同時,我們應該維護父子節點的關係:

void build(ll p,ll l,ll r)

//如果左右區間相同,那麼必然是葉子節點啦,只有葉子節點是被真實賦值的

ll mid=(l+r)>>1;

build(ls(p),l,mid);

build(rs(p),mid+1,r);

//此處由於我們採用的是二叉樹,所以對於整個結構來說,可以用二分來降低複雜度,否則樹形結構則沒有什麼明顯的優化

push_up(p);

//此處由於我們是要通過子節點來維護父親節點,所以pushup的位置應當是在回溯時。

}

為什麼不討論單點修改呢?

因為其實很顯然,單點修改就是區間修改的乙個子問題而已,

即區間長度為1時進行的區間修改操作罷了

那麼對於區間操作,我們考慮引入乙個名叫「lazy tag」(懶標記)的東西

之所以稱其「lazy」,是因為原本區間修改需要通過先改變葉子節點的值,

然後不斷地向上遞迴修改祖先節點直至到達根節點,

時間複雜度最高可以到達o(nlogn)的級別。

但當我們引入了懶標記之後

,區間更新的期望複雜度就降到了o(logn)的級別

甚至會更低.

分塊的思想是通過將整個序列分為有窮個小塊,對於要查詢的一段區間,總是可以整合成k個所分塊與m個單個元素的資訊的並(0<=k,m<=log(n))

那麼我們可以反過來思考這個問題:

對於乙個要修改的、長度為l的區間來說,

總是可以看做由乙個長度為2^log(⌊n⌋)和剩下的元素(或者小區間組成)。

那麼我們就可以先將其拆分成線段樹上節點所示的區間,之後分開處理:

如果單個元素被包含就只改變自己,如果整個區間被包含就修改整個區間

其實好像這個在分塊裡不是特別簡單地實現,

所以我們不需要區分區間還是元素,加個判斷就好。

首先,懶標記的作用是記錄每次、每個節點要更新的值,也就是delta,

但線段樹的優點不在於全記錄(全記錄依然很慢),而在於傳遞式記錄:

整個區間都被操作,記錄在公共祖先節點上;

只修改了一部分,那麼就記錄在這部分的公共祖先上;

如果四環以內只修改了自己的話,那就只改變自己。

之後,如果我們採用上述的優化方式的話,

我們就需要在每次區間的查詢修改時push_down一次,

以免重複或者衝突或者**

那麼對於push_down而言,

其實就是純粹的push_up的逆向思維(但不是逆向操作):

因為修改資訊存在父節點上,所以要由父節點向下傳導llazy tag

那麼問題來了:怎麼傳導push_down呢?

這裡很有意思,

開始回溯時執行push_up,因為是向上傳導資訊;

那我們如果要讓它向下更新,就調整順序,

在向下遞迴的時候push_down;

如此簡單....(逃

inline void f(ll p,ll l,ll r,ll k)

//我們可以認識到,f函式的唯一目的,就是記錄當前節點所代表的區間

inline void push_down(ll p,ll l,ll r)

inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k)

push_down(p,l,r);

//回溯之前(也可以說是下一次遞迴之前,因為沒有遞迴就沒有回溯)

//由於是在回溯之前不斷向下傳遞,所以自然每個節點都可以更新到

ll mid=(l+r)>>1;

if(nl<=mid)update(nl,nr,l,mid,ls(p),k);

if(nr>mid) update(nl,nr,mid+1,r,rs(p),k);

push_up(p);

//回溯之後

}

對於複雜度而言,由於完全二叉樹的深度不超過logn,

那麼單點修改顯然是o(logn)的,

區間修改的話,由於我們的這個區間至多分log(n)個子區間,

對於每個子區間的查詢是o(1)的,所以複雜度自然是o(logn)

不過帶一點常數

沒什麼好說的,由於是資訊的整合,所以還是要用到分塊思想,我實在是不想再碼一遍了qwqqwq

ll query(ll q_x,ll q_y,ll l,ll r,ll p)

#include#include#define maxn 1000001

#define ll long long

using namespace std;

unsigned ll n,m,a[maxn],ans[maxn<<2],tag[maxn<<2];

inline ll ls(ll x)

inline ll rs(ll x)

void scan()

inline void push_up(ll p)

void build(ll p,ll l,ll r)

ll mid=(l+r)>>1;

build(ls(p),l,mid);

build(rs(p),mid+1,r);

push_up(p);

} inline void f(ll p,ll l,ll r,ll k)

inline void push_down(ll p,ll l,ll r)

inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k)

push_down(p,l,r);

ll mid=(l+r)>>1;

if(nl<=mid)update(nl,nr,l,mid,ls(p),k);

if(nr>mid) update(nl,nr,mid+1,r,rs(p),k);

push_up(p);

}ll query(ll q_x,ll q_y,ll l,ll r,ll p)

int main()

case 2:}}

return 0;

}

本文**@

pks'w blog

線段樹模板(模板)

參考部落格 持續更新。外鏈轉存失敗,源站可能有防盜煉機制,建議將儲存下來直接上傳 img xhrgdjcd 1613976863463 區間儲存在陣列中的下標對應為 12 3 4 5 6 7 8 9 10 11 12 13 14 15 四部分單點更新 根據題目的要求編寫自己的pushup,query...

線段樹模板

include include include using namespace std const int size 10010 struct node the node of line tree class linetree void updatem void updateline public ...

線段樹模板

單點更新,區間求最值 include include include include include define n 222222 using namespace std int num n struct tree tree n 4 void push up int root void build...