線段樹的一點總結
線段樹,顧名思義,是根據線段建成的樹。每一個節點都可以是線段。對於單點查詢,區間查詢,單點更新,區間更新都是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線段樹講稿】統計的力量-線段樹》,在那個裡面,我感覺線段樹的性質被淋漓盡致地展現出來,十分精彩。
相關文章
- 線段樹(毒瘤)總結
- 線段樹模板總結
- 動態開點線段樹
- 資料結構-線段樹資料結構
- 資料結構之樹( 線段樹,字典樹)資料結構
- 線~段~樹
- 線段樹
- 動態開點線段樹說明
- hdu 1698 線段樹 一段更新染色
- B+樹的幾點總結
- 資料結構--線段樹合併資料結構
- 資料結構(勢能線段樹)資料結構
- HDU 3074 Multiply game(線段樹 單點更新)GAM
- 線段樹模板
- 線段樹--RMQMQ
- 01 線段樹
- 線段樹 hate it
- 【模版】線段樹
- 郝玩的資料結構——線段樹(待upd)資料結構
- HDU 1166 敵兵佈陣 (線段樹 插點問線)
- ut.cpp 最大線段並減線段交 [線段樹]
- 一些線段樹典(求求了區域賽遇到線段樹不要被卡了)
- 暫存一下線段樹模板
- 權值線段樹
- 線段樹筆記筆記
- Segment Tree(線段樹)
- 線段樹入門
- 李超線段樹
- 線段樹進階
- 【資料結構】可持久化線段樹初步資料結構持久化
- 2024年暑假關於線段樹和樹狀陣列的小知識點陣列
- 洛谷題單指南-線段樹-P3373 【模板】線段樹 2
- 線段樹擴充套件套件
- 第二課——線段樹
- 線段樹簡單思路
- 深入理解線段樹
- POJ 3667 Hotel 線段樹
- poj 2667 hotel 線段樹
- 線段樹(超詳解)