線段樹 總結

2021-10-04 18:21:29 字數 3276 閱讀 8633

一、線段樹的概論

二、線段樹的性質

三、線段樹的建樹

四、線段樹的單點修改

五、線段樹的區間查詢

六、線段樹的區間修改

七、**實現

假設有編號從1到n的n個點,每個點都存了一些資訊,用[l,r]表示下標從l到r的這些點。

線段樹的用處就是,對編號連續的一些點進行修改或者統計操作,修改和統計的複雜度都是o(log2(n)).

線段樹的原理,就是,將[1,n]分解成若干特定的子區間,然後,將每個區間[l,r]都分解為少量特定的子區間,通過對這些少量子區間的修改或者統計,來實現快速對[l,r]的修改或者統計。

由此看出,用線段樹統計的東西,必須符合區間加法,否則不可能通過分成的子區間來得到[l,r]的統計結果。

符合區間加法的例子:

數字之和——總數字之和 = 左區間數字之和 + 右區間數字之和

最大值——總最大值=max(左區間最大值,右區間最大值)

不符合區間加法的例子:

眾數——只知道左右區間的眾數,沒法求總區間的眾數

如果觀察一下線段樹(無奈這裡木有圖),會發現除去線段樹的最後一層,整顆線段樹是一顆完全二叉樹,樹深為o(logn),因此我們可以用「父子二倍」標號方法:

1.根節點編號為1

2.編號為x的節點的左子節點編號為x*2,右子節點編號為x*2+1

線段樹的基本用途是對序列進行維護,支援查詢與修改指令。給定乙個長度為n的序列a,我麼可以在序列[1,n]上建立一顆線段樹,每個葉節點[i,]儲存a[i]的值。線段樹的二叉樹結構可以很方便地從下往上傳遞資訊。以區間最大值為例,d(l,r)等於max,顯然d(l,mid)=max(d(l,mid),d(mid+1,r))

下面的**建立了線段樹並保留了最大值:

單點修改是一條形如「c x v"的指令,表示把a[x]的值修改為v

時間複雜度為o(logn)

區間查詢是一條形如」q l r"的指令,例如查詢序列a在區間[l,r]上的最大值,即max.我們只需要從根節點開始

1.若「l,r"完全覆蓋了當前節點代表的空間,則立即回溯,並且將該節點的值為候選答案

2.若左子節點與[l,r]有重疊部分,則遞迴左子節點

3.若右子節點與[l,r]有重疊部分,則遞迴右子節點

線段樹的區間修改也是將區間分成子區間,但是要加乙個標記,稱作懶惰標記(lazy_tag)

標記的含義:本節點的統計資訊已經根據標記更新過了,但是本節點的子節點仍需要進行更新。

即,如果要給乙個區間的所有值都加上k,那麼,實際上並沒有給這個區間的所有值都加上k,而是打個標記,記下來,這個節點所包含的區間需要加k.打上標記後,要根據標記更新本節點的統計資訊,比如,如果本節點維護的是區間和,而本節點包含5個數,那麼,打上+k的標記之後,要給本節點維護的和+5k。

(1)定義

#define n 100005

int a[n],n,n;//原陣列

int sum[n<<2];

int add[n<<2];//懶惰標記

(2)建樹

void pushup(int rt) 

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

int m=(l+r)>>1;

//左右遞迴

build(l,m,rt<<1);

build(m+1,r,rt<<1|1);

//更新

pushup(rt);

}

(3)點修改

void update(int l,int c,int l,int r,int rt)

int m=(l+r)>>1;

//根據條件判斷往左子樹呼叫還是往右

if(l <= m) update(l,c,l,m,rt<<1);

else update(l,c,m+1,r,rt<<1|1);

pushup(rt);//子節點更新了,本節點也需要更新資訊

}

(4)下推標記

void pushdown(int rt,int ln,int rn)

}

(5)區間修改

void update(int l,int r,int c,int l,int r,int rt)

int m=(l+r)>>1;

pushdown(rt,m-l+1,r-m);//下推標記

//判斷左右子樹跟[l,r]有無交集

if(l <= m) update(l,r,c,l,m,rt<<1);

if(r > m) update(l,r,c,m+1,r,rt<<1|1);

pushup(rt);//更新

}

(6)區間查詢

int query(int l,int r,int l,int r,int rt)

int m=(l+r)>>1;

//下推標記

pushdown(rt,m-l+1,r-m);

int ans=0;

if(l <= m) ans+=query(l,r,l,m,rt<<1);

if(r > m) ans+=query(l,r,m+1,r,rt<<1|1);

return ans;

}

(7)函式呼叫

//建樹 

build(1,n,1);

//點修改

update(l,c,1,n,1);

//區間修改

update(l,r,c,1,n,1);

//區間查詢

int ans=query(l,r,1,n,1);

線段樹總結

線段樹總結 線段樹的原理就是每乙個區間都可以被分成若干個不相交連續區間 重要 線段樹維護的資料 1.自身結構的資料 比如 左兒子 右兒子的編號 2.懶惰標記 整段區間都變成乙個值 或者將要進行什麼操作 根據每次操作的型別 把操作的區間分成若干個不連續的區間 然後把操作的標記賦值給相應的區間 3.答案...

線段樹總結

線段樹的入門級 總結 線段樹是一種二叉搜尋樹,與區間樹相似,它將乙個區間劃分成一些單元區間,每個單元區間對應線段樹中的乙個葉結點。對於線段樹中的每乙個非葉子節點 a,b 它的左兒子表示的區間為 a,a b 2 右兒子表示的區間為 a b 2 1,b 因此線段樹是平衡二叉樹,最後的子節點數目為n,即整...

線段樹總結

解決的題目 對區間所對應的一些資料進行修改,查詢。基本步驟 先建樹,然後插入資料,然後更新,查詢。關鍵部分 用線段樹解題,關鍵是要想清楚每個節點要存哪些資訊以及這些資訊如何高效更新,維護,查詢。不要一更新就更新到葉子節點,那樣更新效率最壞就可能變成o n 的了。建樹的方式 1 陣列 若根節點下標為0...