學習筆記 線段樹

2022-06-02 03:00:13 字數 4334 閱讀 7846

update 20190926 更新對線段樹本質的理解

搬運自自己一年前的文章(

\(part -1\) 約定

\[mid=(l+r)/2

\]\(part 0\) 介紹

線段樹(\(segment\)

\(tree\))是一種用來處理區間問題的資料結構。這棵二叉樹功能非常強大,支援的有

-----分割線----

線段樹的本質是什麼?

大概就是乙個可以支援資訊合併,以分治(亦有人說是分塊,這裡看個人理解)為思想的資料結構。與樹狀陣列的區別是可以不滿足可減性

舉個例子,區間最大值可以用線段樹維護,因為乙個區間的最大值可以由直接由下面的得出。

而連續最大和呢?不能由下面的直接得出,所以不能直接維護。當然,間接維護還是支援的。

-----分割線----

比如說有乙個長度為n的數列,有2類操作:

1、將某個區間中的數都加上\(x\)。

2、詢問某個區間中所有數的和並輸出。

資料範圍:

\(0 \le n \le 100000\)

\(0 \le m \le 100000\)

(n,數列個數,m,運算元)

上面這一道題就是線段樹的模板,毫無疑問,如果暴力搞,時間複雜度是\(o(nm)\)的,承受不了,但是如果使用線段樹的話就可以在\(o(mlogn)\)的時間內解決這道題。那麼接下來我們就來講一講線段樹究竟是如何構造與實現的。

\(part 1\) 入門

\(part 1.1\) 建樹

首先觀察這顆線段樹

可以看出:

1.這顆樹的每乙個非葉節點的點,都有兩個兒子。

2.設該節點(非葉節點)表示\([l,r]\)的資訊,則左兒子表示\([l,mid]\)的資訊,右兒子表示\([mid+1,r]\)的資訊。

**3.葉結點表示單獨乙個節點的資訊

**回歸正題,我們怎麼建樹呢?

(對於普通線段樹,設該節點的位置為\(x\),則左兒子的位置為\(2x\),右兒子的位置為\(2x+1\)。)

我們可以通過遞迴建樹,當建到葉結點時賦值並返回,在返回時,我們可以順便求出其他節點的資訊了。

**

void maketree(long long t,long long l,long long r)

//葉結點

long long mid=(l+r)/2;

maketree(t*2,l,mid);maketree(t*2+1,mid+1,r);//遞迴左兒子和右兒子

tree[t]=tree[t*2]+tree[t*2+1];//求和

}

\(part 1.2\) 單點操作

首先,思考一下,上圖中若要更新乙個位置(葉結點)的資訊,哪些地方需要更新?

若要更改5號節點,更新的位置如下(橙色代表更新)

可以看出,這其實就像一條鏈,所以,我們就可以逐層丟鍋遞迴下去,然後再逐層遞迴上來更新資訊。就完成了線段樹的單點修改

**如下

//此程式以求最大值為例

void update(int t,int l,int r,int x,int y)

//到葉結點時直接修改

int mid=(l+r)/2;

if (x<=mid) update(t*2,l,mid,x,y);

else update(t*2+1,mid+1,r,x,y);

tree[t]=max(tree[t*2],tree[t*2+1]);//更新非葉節點資訊

}

那如何單點查詢呢?其實和單點修改一樣,我們逐層往下遞迴就能找到答案了。

**和單點修改差不多,只需刪掉更新部分,增加返回部分(\(return\))就可以了。

\(part 2\) 提高

看完上面這些,恭喜你,可以寫乙個粗拙的線段樹了,然而,這些解決上面的題還不夠用,所以接著往下看吧

\(part 2.1\) 區間更新

區間更新其實可以暴力改成單點更新。然而,你可能發現,目前為止,只有葉節點有用。那麼,區間更新就需要用到其他節點了。

想象一下,當你要傳達通知到1,2,3那兒,而剛好,他們在同乙個公司。而且公司只有那三個人,那我們就可以直接傳到公司了。感覺不是一般的懶!

而當我們進行區間更新時,我們可能發現某些節點的所有子節點都在更新範圍,那麼此時我們可以先在那乙個節點打乙個標記,等到用到時才往下進行更新,這就是lazy標記。

**

void add(long long t,long long l,long long r,long long al,long long ar,long long c)

//新增lazy標記

free(t,l,r);//下放lazy標記

long long mid=(l+r)/2;

if (ar<=mid) add(t*2,l,mid,al,ar,c);else

if (al>mid) add(t*2+1,mid+1,r,al,ar,c);else

tree[t]=tree[t*2]+tree[t*2+1];//更新資訊

}

請想一想,addnote函式中需要新增什麼?為什麼?

void addnote(long long t,long long l,long long r,long long c)

\(part 2.2\) 區間更新

我們可以把各個查詢節點的資訊進行整合,即可得出答案。當然,我們不必每次都下到葉節點,可以通過其他節點

(即:某些所有子節點都在查詢範圍的節點)的資訊得出。

需要注意的是,無論是更新還是查詢,我們都需要進行釋放lazy標記的操作,防止出現錯誤。釋放lazy標記,就是把當前節點的標價刪去,把原標記下放至此節點的子節點。

//釋放懶標記

void free(long long t,long long l,long long r)

//查詢(求和)

long long ask(long long t,long long l,long long r,long long al,long long ar)

}

\(part 3\) 一些小事情

\(part 4\) 結束

最終線段樹就是這樣啦:

#includeusing namespace std;

long long tree[400400],lazy[800800],a[100100];

long long n,m,cz,l,r,c;

void maketree(long long t,long long l,long long r)

long long mid=(l+r)/2;

maketree(t*2,l,mid);maketree(t*2+1,mid+1,r);

tree[t]=tree[t*2]+tree[t*2+1];

}void addnote(long long t,long long l,long long r,long long c)

void free(long long t,long long l,long long r)

void add(long long t,long long l,long long r,long long al,long long ar,long long c)

free(t,l,r);

long long mid=(l+r)/2;

if (ar<=mid) add(t*2,l,mid,al,ar,c);else

if (al>mid) add(t*2+1,mid+1,r,al,ar,c);else

tree[t]=tree[t*2]+tree[t*2+1];

}long long ask(long long t,long long l,long long r,long long al,long long ar)

}int main()

else

{cin>>l>>r;

cout如果有興♂趣,可以試著做一下3373

參考資料((

線段樹學習筆記

線段樹是一種 二叉搜尋樹 與區間樹 相似,它將乙個區間劃分成一些單元區間,每個單元區間對應線段樹中的乙個葉結點。使用線段樹可以快速的查詢某乙個節點在若干條線段中出現的次數,時間複雜度為o logn 而未優化的 空間複雜度 為2n,因此有時需要離散化讓空間壓縮。以下筆記摘自lcomyn神犇部落格 1....

線段樹學習筆記

本文筆記在參考一步一步理解線段樹 tenos的基礎上形成 線段樹,也是二叉搜尋樹的一種,是基於陣列,但是優於陣列的一種資料結構。同時結合預處理 時間複雜度一般在o n 使得從原來陣列的o n 的查詢和更新複雜度降到了o logn 在處理很大資料量的資料更新和查詢最值方面變得簡單,值得一提的是,它的構...

線段樹學習筆記

線段樹是一種維護區間的資料結構,且滿足二叉樹的全部性質 下圖是一棵維護區間 1 6 1,6 的線段樹 格式 idl ri dl r我們可以發現,對於每個節點 k k 來說,其左節點編號為2k role presentation style position relative 2k2 k,右節點編號為...