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,右節點編號為...