線段樹學習筆記 2019 8 11

2021-09-26 02:19:16 字數 4185 閱讀 7569

1、線段樹簡介

線段樹是一種二叉搜尋樹,它將乙個區間劃分成一些單元區間,每個單元區間對應線段樹中的乙個葉結點。

對於線段樹中的每乙個非葉子節點[a,b],它的左兒子表示的區間為[a,(a+b)/2],右兒子表示的區間為[(a+b)/2+1,b]。因此線段樹是平衡二叉樹,最後的子節點數目為n,即整個線段區間的長度。

如果線段樹中的乙個非葉子節點編號為x,他的左兒子的編號為(x<<1),他的右兒子編號為(x<<1|1)。

2、some questions

a)為什麼線段樹要開4倍空間?  線段樹並不一定是完全二叉樹,可能會出現以下這種情況:

有些編號比較小的節點變成了葉節點,但比他大的編號裡出現了非葉結點,所以會用到更大的編號。

b)線段樹的時間複雜度:用線段樹對「編號連續」的一些點,進行修改或者統計操作,修改和統計的複雜度都是o(log n)

c)如何設定lazytag?如何用設定的lazy_tag更新所需資料值?  lazytag的設定,主要是要求:求sum的時候,根據從父區間傳下來的lazy_tag,能正確更新子區間所有內部資料值和他的lazy值,從而正確求出sum

3、線段樹的組成

struct node

tree[n*4+10];

build 函式

void build(int l,int r,int x)     ///x為當前節點編號

build(l,(l+r)/2,x*2);

build((l+r)/2+1,r,x*2+1);

tree[x].w=tree[x*2].w+tree[x*2+1].w;

}

區間查詢

void sum(int k)

int m=(tree[k].l+tree[k].r)/2;

if(x<=m) sum(k*2);

if(y>m) sum(k*2+1);

}

區間修改

修改的時候只修改對查詢有用的點,這是區間修改的關鍵思路。為了實現這個,我們引入乙個新的狀態——懶標記

遞迴到這個節點時,只更新這個節點的狀態,並把當前的更改值累積到標記中。

當需要遞迴這個節點的子節點時,標記下傳給子節點。

懶標記的下傳

①當前節點的懶標記累積到子節點的懶標記中。

②修改子節點狀態。原狀態+子節點區間點的個數*父節點傳下來的懶標記。

③父節點懶標記清0。

a)懶標記下傳

void down(int k)

b)區間查詢

void add(int k)

if(tree[k].f) down(k);//懶標記下傳。只有不滿足上面的if條件才執行,所以一定會用到當前節點的子節點

int m=(tree[k].l+tree[k].r)/2;

if(a<=m) add(k*2);

if(b>m) add(k*2+1);

tree[k].w=tree[k*2].w+tree[k*2+1].w;//更改區間狀態

}

完整的**:

#includeusing namespace std;

int n,p,a,b,m,x,y,ans;

struct node

tree[400001];

inline void build(int k,int ll,int rr)//建樹

int m=(ll+rr)/2;

build(k*2,ll,m);

build(k*2+1,m+1,rr);

tree[k].w=tree[k*2].w+tree[k*2+1].w;

}inline void down(int k)//標記下傳

inline void ask_point(int k)//單點查詢

if(tree[k].f) down(k);

int m=(tree[k].l+tree[k].r)/2;

if(x<=m) ask_point(k*2);

else ask_point(k*2+1);

}inline void change_point(int k)//單點修改

if(tree[k].f) down(k);

int m=(tree[k].l+tree[k].r)/2;

if(x<=m) change_point(k*2);

else change_point(k*2+1);

tree[k].w=tree[k*2].w+tree[k*2+1].w;

}inline void ask_interval(int k)//區間查詢

if(tree[k].f) down(k);

int m=(tree[k].l+tree[k].r)/2;

if(a<=m) ask_interval(k*2);

if(b>m) ask_interval(k*2+1);

}inline void change_interval(int k)//區間修改

if(tree[k].f) down(k);

int m=(tree[k].l+tree[k].r)/2;

if(a<=m) change_interval(k*2);

if(b>m) change_interval(k*2+1);

tree[k].w=tree[k*2].w+tree[k*2+1].w;

}int main()

else if(p==2)

else if(p==3)

else}}

4、一些例題a)hdu1166 非常經典的題,可以用樹狀陣列做,當然線段樹入門的小白(like me 也可以用來練手。

主要用到單點修改和區間查詢,所以不需要懶標記,主要**非常模版,如下:

void build(int l,int r,int k)      ///建樹

int m=(l+r)/2;

build(l,m,k*2);

build(m+1,r,k*2+1);

tree[k].w=tree[k*2].w+tree[k*2+1].w;

}void add(int k) ///單點修改

int m=(tree[k].l+tree[k].r)/2;

if(x<=m) add(k*2);

else add(k*2+1);

tree[k].w=tree[k*2].w+tree[k*2+1].w;

}inline void ask_interval(int k) ///區間查詢

int m=(tree[k].l+tree[k].r)/2;

if(a<=m) ask_interval(k*2);

if(b>m) ask_interval(k*2+1);

}

b)hdu 1754  維護區間最大值+單點修改 ac**:

void build(int l,int r,int k)

int m=(l+r)/2;

build(l,m,k*2);

build(m+1,r,k*2+1);

tree[k].w=max(tree[k*2].w,tree[k*2+1].w);

}void add(int k)

int m=(tree[k].l+tree[k].r)/2;

if(x<=m) add(k*2);

else add(k*2+1);

tree[k].w=max(tree[k*2].w,tree[k*2+1].w);

}inline void ask_interval(int k)

int m=(tree[k].l+tree[k].r)/2;

if(a<=m) ask_interval(k*2);

if(b>m) ask_interval(k*2+1);

}

借鑑:

線段樹學習筆記

線段樹是一種 二叉搜尋樹 與區間樹 相似,它將乙個區間劃分成一些單元區間,每個單元區間對應線段樹中的乙個葉結點。使用線段樹可以快速的查詢某乙個節點在若干條線段中出現的次數,時間複雜度為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,右節點編號為...