線段樹的一點總結
線段樹,顧名思義,是根據線段建成的樹。每一個節點都可以是線段。對於單點查詢,區間查詢,單點更新,區間更新都是O(logn)級別的,所以對於大多數區間操作比較大的題,都可以用線段樹解決。
0.性質
對於每一個非葉子節點下標為i的節點,它的左兒子的下標必定為I<<1,右兒子的下標必定為I<<1|1.
1.定義
const int MAXN = 100005;//線段最大長度
struct Node{
int l;//區間的左端點
int r;//區間的右端點
int v;//區間儲存的資料
}tree[MAXN<<2];//一般是開4倍,但是聽大牛說最多隻有3點幾倍
2.建樹
void Buildtree(int i,int left,int right)//當前節點的下標及左右端點的座標
{
tree[i].l=left;//給左右端點賦值
tree[i].r=right;
if(left==right)
{
scanf("%d",&tree[i].v);//給葉子節點賦值
return ;
}
int mid=(left+right)>>1;
Buildtree(i<<1,left,mid);//折半向左右遞迴建樹
Buildtree(i<<1|1,mid+1,right);
}
3.區間查詢
int Query(int i,int left,int right)//當前區間的下標和需要查詢的左右端點
{
if(tree[i].l>=left&&tree[i].r<=right)//噹噹前區間已經被覆蓋的時候,直接return節點資訊
return tree[i].v;
int mid=(tree[i].l+tree[i].r)>>1;
if(left<=mid)Query(i<<1,left,right);//分別向左右子節點查詢
if(mid<right)Query(i<<1|1,left,right);
}
4.單點更新
void Modi(int i,int pos,int v)//當前區間的下標,需要修改的位置以及需要被修改成的值
{
if(tree[i].l==pos&&tree[i].r==pos)//找到該位置,修改然後返回
{
tree[i].v=v;
return ;
}
int mid=(tree[i].l+tree[i].r)>>1;
if(pos<=mid)Modi(i<<1,pos,v);//分別向左右子節點查詢
if(mid<pos)Modi(i<<1|1,pos,v);
}
小結:以上就是線段樹最基礎的實現,可以做到單點更新,區間查詢。初學線段樹,重點就是要體會遞迴的思想。
推薦題目:HDU 1166,HDU 2795,HDU 1394,HDU 1754
5.區間更新
這個是初學者的一道坎,學會了這個,才算踏入了線段樹的大門。我們需要用到一個標記,又叫LazyTag,看名字就知道,這是一個很懶的標記,那麼這個標記是用來幹什麼的呢?我們稍後再講。首先我們來討論一下區間更新的時間複雜度。很容易想到,如果我們把區間看做一個一個的點,那麼只需要一個迴圈遍歷所有的點然後對於每一個點單點更新即可,那麼時間複雜度是多少呢?對於長度為length的區間更新,這樣做的時間複雜度為O(length*logn)如果對於多次長度很大的查詢,這樣做顯然很笨重,那麼這個時候我們就需要用到LazyTag了。我們首先在我們最開始定義的結構體中加上一個成員Tag,然後在函式Buildtree裡面加上它的初始化,一般的,我們初始化為0,但是對於具體情況還是要具體分析,不能一味的套模板。
void PushDown(int st)
{
if(tree[st].tag!=0)
{
tree[st<<1].v+=tree[st].v;//向左右子節點傳遞標記
tree[st<<1].tag+=tree[st].tag;
tree[st<<1|1].v+=tree[st].v;
tree[st<<1|1].tag+=tree[st].tag;
tree[st].tag=0;//傳遞過後,標記被清零
}
}
void Update(int i,int left,int right,int v)
{
if(tree[i].l>=left&&tree[i].r<=right)
{
tree[i].v+=v;//一般做題的時候當前節點的值是要被更新後才打上標記
tree[i].tag+=v;//打上標記
return ;
}
PushDown(i);
int mid=(tree[i].l+tree[i].r)>>1;
if(left<=mid)Update(i<<1,left,right,v);//左右向左右子節點遞迴
if(mid<right)Update(i<<1|1,left,right,v);
}
推薦題目:HDU 1556,HDU 1698,POJ 3468
6.區間合併
6.區間合併
從這個地方開始就有點困難了,一般的線段樹只能對給定的區間進行操作,但是如果我們需要查詢滿足某種需要的最大區間呢?那麼我們就需要對具有相同性質的區間進行合併。我們一般是向我們定義的結構體中新增三個成員,lsum,rsum,msum。這三個成員分別表示當前區間從左,右端點開始滿足條件的最大長度和整個區間滿足條件的最大長度。
對於子節點,我們可以很容易的判斷出它是否滿足條件,那麼我們就從子節點向上更新即可,例如:
void PushUp(int i,int len)//更新的節點下標和更新的區間長度
{
tree[i].lsum=tree[i<<1].lsum;//當前節點的lsum是由左子節點貢獻而來
if(tree[i].lsum==(len-(len>>1)))tree[i].lsum+=tree[i<<1|1].lsum;//如果lsum整個區間的長度相同,那麼我們需要加上右子節點的lsum
tree[i].rsum=tree[i<<1|1].rsum;
if(tree[i].rsum==(len>>1))tree[i].rsum+=tree[i<<1].rsum;
tree[i].msum=max(tree[i<<1].rsum+tree[i<<1|1].lsum,max(tree[i<<1].msum,tree[i<<1|1].msum))//整個區間的msum就是左右子區間的msum和左子區間的rsum加上右區間的lsum中的最大值
}
當然,區間合併也不是直接套用模板就行的,我們需要對具體問題進行分析,才能確定如何進行區間合併。
推薦題目:HDU 3308,POJ 3667
7.掃描線
這個一般是和離散化聯絡起來的,想象出一個線,沿著一個方向掃過去。一般都是與離散化的方向垂直。思考清楚然後注意一下資料突變的點就沒什麼難的了。
(這個東西沒有什麼固定的套路(可能是因為我太弱不知道),還是需要多寫,熟練了就好)
推薦題目:HDU 1542 ,HDU 1828
總結:線段樹雖然不算高階資料結構,但是很好用,能夠解決很多與區間有關的問題,因為它優秀的時間複雜度,熟練掌握它能加深您對遞迴和二叉樹儲存的理解。當然,以上這些並不是線段樹的全部,如果您熟練掌握了以上線段樹的技巧之後,還意猶未盡的話,您可以去看看《【zkw線段樹講稿】統計的力量-線段樹》,在那個裡面,我感覺線段樹的性質被淋漓盡致地展現出來,十分精彩。
相關文章
- 線段樹知識點總結
- [乾貨] 線段樹知識點總結
- 線段樹(毒瘤)總結
- 線段樹模板總結
- 線段樹經驗及總結
- 動態開點線段樹
- 資料結構-線段樹資料結構
- 資料結構之樹( 線段樹,字典樹)資料結構
- 動態開點線段樹說明
- 線段樹 hate it
- 線段樹模板
- 01 線段樹
- B+樹的幾點總結
- 線段樹筆記筆記
- 線段樹入門
- 權值線段樹
- 【資料結構】可持久化線段樹初步資料結構持久化
- POJ 2828 Buy Tickets 線段樹入門(建樹稍微有點抽象)抽象
- HDU 3074 Multiply game(線段樹 單點更新)GAM
- (hdu 1754) I Hate It(線段樹基礎,單點更新)
- hihocoder 1078 線段樹的區間修改 (線段樹 區間更新 模板)
- 深入理解線段樹
- 線段樹入門理解
- 第二課——線段樹
- 簡單的線段樹應用
- 最近一段時間的總結
- 線段樹 - 多組圖帶你從頭到尾徹底理解線段樹
- Duplicate的一點總結
- ash的一點總結
- 一點總結
- 關於線段樹基礎
- 淺談線段樹(Segment Tree)
- hdu 1754 I Hate It (線段樹)
- [18/03/24] 線段樹模板
- 網頁開發的階段總結(一)網頁
- 一類子樹問題的總結
- 【軟考第一階段總結】
- 第一階段衝刺總結