刪除多餘線段 線段樹

2021-10-14 16:46:14 字數 3717 閱讀 6630

2020-3-24 更新**

一,簡介

二,作用及原理

三,線段樹中最重要的----延遲標記

四,額外說明

五,具體實現

一,簡介
線段樹,一種樹狀資料結構,顧名思議,樹的每乙個節點儲存的是線段的某種資訊,因此,你需要宣告乙個結構體來代表樹上的每乙個節點,最基本的資訊有線段的左端點右端點,常用的有線段區間的元素總和,最值之類的。

設父節點的左右端點為l和r,則設mid=(l+r)/2,左葉子節點兩端為l和mid,右葉子節點兩端為mid和r。

乙個下標從1開始,長度為10的陣列構成的線段樹例子

我的建議是構造線段樹時的陣列下標從1開始,在上圖中,[1 , 10]為1,[1 , 5]為2,[6 , 10]為3,以此類推,這樣子構造,若父節點的下標為x,左右子節點則分別為2*x和2*x+1,在向上遞迴時,子節點的下標直接除以2就是父節點的下標(左葉子節點均為偶數,右葉子節點均為奇數)。

二,作用及原理
線段樹的作用是進行區間快速的修改,查詢,當區間的左端點與右端點相等時,即單點的修改與查詢,也適用於線段樹。

可以發現,線段樹由於其自身性質,任意兩個沒有祖先關係的節點之間,他們所儲存的線段範圍是不會相交的。因此,這縮短了改查的時間,從 n 降低到 log(n),比如在上圖中,要查詢線段[4 , 10]的資訊(可能是總值,可能是最值等等),只需要往下查詢到[4 , 5]和[6 , 10]兩個節點,遞迴時總結兩個節點的資訊即可。

在進行線段樹的操作時,一般分為三種情況來,設需要操作的線段的左右端點分別為targ_l和targ_r,當前查詢的節點的線段端點為l和r。

舉個例子,我們現在所求的是線段中的元素總和。

首先宣告結構體,其中:

int l, r, val ;分別為線段左右端點和線段中元素總和。

查詢函式:int interval_query(int targ_l, int targ_r, int l, int r, int tot)

引數為目標左端點,目標右端點,當前左端點,當前右端點,目前節點的下標

內容:

if(targ_l > r || targ_r < l)return 0;

if(targ_l <= l && targ_r >= r)return node[tot].val; node為線段樹結構體

int mid=(l + r) / 2;

int a=interval_query(targ_l, targ_r, l, mid, tot * 2);

int b=interval_query(targ_l, targ_r, mid + 1, r, tot * 2 + 1);

return a + b;

上述**就是區間查詢的**。

可以發現,當操作的區間左右端點相同,比如[4 , 4],即單點操作,所需的運算元也是

三,線段樹中最重要的----延遲標記
可以注意到,當線段樹進行暴力區間的修改時,要修改大量葉子節點的屬性,修改一次理論次數

延遲標記相當於先打借條,還是上述[4 , 10]和區間求和的例子,若是無延遲標記的情況下,不僅需要修改[4 , 5]和[6 , 10]的值,還需要修改其父節點和子節點的值,即除了[1 , 5]的左子樹外的節點都要修改,但引進延遲標記後,[4 , 5]和[6 , 10]的父節點的資訊在從根節點遞迴來的時候進行更新,到了[4 , 5]和[6 , 10]的時候,為這兩個節點打上延遲標記,代表的意思是,[4 , 5]和[6 , 10]的所有子節點,需要增加所打的延遲標記的值,這相當於打借條,當查詢的時候查詢到有延遲標記的節點時,進行本節點的更新,再將延遲標記傳給它的子節點(如果沒有子節點即l==r則return)。相當於在查詢過程中順便更新了值,不會增加多餘的運算元,如果程式全程沒有查詢到該帶有延遲標記的節點,那麼這個'借條'也就不兌現了。

因此,線段樹的區間修改和區間查詢的**中除了上述三種情況外,還需增加對延遲標記的操作,結構體中也需要加入延遲標記變數。

int lazy ;

具體操作(以求和為例):

修改時:當當前所查詢的區間包圍於目標區間中時,打上延遲標記。

node[tot] . lazy += val ;

查詢時:當當前節點存在延遲標記時,該節點

node[tot * 2] . val += node[tot] . lazy;

node[tot * 2 + 1] . val += node[tot] . lazy;

然後判斷該節點是否有子節點,即(l==r),如果有子節點,將延遲標記傳遞到子節點上

否則不管。最後將

node[tot] . lazy = 0; 。

四,額外說明
需要使用線段樹的題目並不會很隱晦,常常不難看出來,但是變種比較多,所需的線段資訊的不同會導致之間**差異較大,因此對於線段樹,需要理解透徹其中的操作,要會延遲標記的合理使用,不同於fft或最大流等題目,有模板可套,線段樹更多還是依賴於題目的實際情況來寫**。

線段樹節點大小要開到原陣列的四倍,這是樹狀結構決定的(注意處理邊界情況,即判斷l是否等於r,如果等於即無子節點,否則有子節點)。

注意類變數要宣告在全域性中,一般情況下,點數到十萬無法宣告區域性變數。

文中**電腦上看比較方便。

五,具體實現
例子為區間求和和區間最(大)值。**封裝於類interval_play中,類中的線段樹類為segtree,帶有四個變數,val,l,r 和 lazy

區間求和**:

struct segnode;

struct segtree

return;

} n[x].val += (n[x].r - n[x].l + 1) * y;

n[x].lazy += y;

} void push(ll x)

void pull(ll x)

void update(ll l, ll r, ll x, ll v)

} ll query(ll l, ll r, ll x)

}st;

區間最值**:

struct segnode;

struct segtree

return;

} n[x].val += y;

// n[x].val += (n[x].r - n[x].l + 1) * y;

n[x].lazy += y;

} void push(ll x)

void pull(ll x)

void update(ll l, ll r, ll x, ll v)

} ll query(ll l, ll r, ll x)

}st;

線段樹 02 構建線段樹

public inte ce merger 不能再縮小的基本問題是 對treeindex指向的節點的情況進行討論 public class segmenttree 在treeindex的位置建立表示區間 l.r 的線段樹 private void buildsegmenttree int treei...

線段樹 01 線段樹基礎

物理上 public class segmenttree public int getsize public e get int index 返回完全二叉樹的陣列表示中,乙個索引所表示的元素的左孩子節點的索引 private int leftchild int index 返回完全二叉樹的陣列表示中...

線段樹和zkw線段樹

好啦,我們就開始說說線段樹吧 線段樹是個支援區間操作和查詢的東東,平時的話還是蠻實用的 下面以最基本的區間加以及查詢區間和為例 線段樹顧名思義就是棵樹嘛,葉子節點是每個基本點,它們所對應的父親就是它們的和,具體如下圖 但是對於這樣的線段樹來說,操作所需的時間是遠達不到我們的要求的 會被t 因為我們會...