線段樹總結

2022-02-02 09:50:22 字數 3939 閱讀 9650

線段樹是什麼?

顧名思義,就是把一顆樹拆成若干個點段,

每乙個父結點可以包含其子節點的資訊(看你要表示什麼了),例如該父結點的全部子節點的值之和,該父節點範圍內子節點的最大值,那麼就可以採取一些例如區間查詢,區間修改,單點查詢,單點修改的操作了,顯然是用空間來換時間的演算法(有了父節點就不用一直追溯到字節點,時間效率會提公升很多,但是由於開了很多沒必要的父節點,空間上比一般演算法會多一些),那麼既然大致理解了線段樹的含義,那麼就看一下基本操作了~

線段樹的根節點由圖(挺醜的,看得懂就好..)可以看出是編號為1的節點,這並不是隨機,單純的為了好操作,每乙個父節點的左右子樹的編號分別為:rt<<1, rt << 1 | 1,其中rt表示的是父節點在結構體裡面的編號,要是還不理解的話,這樣用動態的思路考慮一下,首先根節點的編號是1,父節點需要向下建樹推子節點,兩個子節點分別是2(1 << 1),3(1 << 1 | 1),依次推下去,2的子節點是4(2 << 1),5(2 << 1 | 1),3的子節點是6(3 << 1),7(3 << 1 | 1),說白了這一方面是為了建一顆完全二叉樹,一方面也是好寫(好寫嗎..)。

來乙個建樹**來看一看:

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

long long mid = (l + r) >> 1;//取中點

build(rt << 1, l, mid);//遞迴左子樹

build(rt << 1 | 1, mid + 1, r);//遞迴右子樹

tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];//這個是區間求和,要是求區間最大值就換成max就好了

}

到了這裡大家應該理解了線段樹大致是個什麼東西了吧,個人認為挺好理解的。

這裡就不單獨寫單點修改了單點查詢了,畢竟沒啥用,而且會了區間修改查詢之後把l和r換成一樣的不就好了嗎(偷懶)。

區間修改相信初學者一開始不好理解,我先放**再解釋

void update(long long rt, long long l, long long r, long long w)

void pushdown(long long rt,long long l, long long r)

void modify(long long rt,long long l,long long r,long long s,long long t,long long w)

pushdown(rt, l, r);

long long mid = (l + r) >>1;

if(s <= mid) modify(rt << 1, l, mid, s, t, w);//mid在左端點的右面且l小於左端點

if(t > mid) modify(rt << 1 | 1, mid + 1, r, s, t, w);//mid在右端點左面且大於r右端點

tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];//區間求和

}

那麼lazy標記到底是什麼呢?要知道一點,線段樹修改時並不會直接修改到葉子節點,那樣時間效率並不高,也失去了父親節點存在的意義,當父親節點的區間在要詢問的區間內部的時候,只會修改父節點的值而不會更新子節點的值,也就是說,當子節點需要更新的時候才會採取操作,那麼lazy標誌就是記錄的父節點欠子節點的值,這裡可能不太好理解,可以自己模擬一下過程,按照**模擬一下就懂了(懶了),別人再怎麼解釋不懂還是不懂,自己實際去模擬一下,多品,細品,就會恍然大悟(霧)。這裡具體還是看**和模擬為主。

區間查詢相對於區間修改就好理解的多,解釋看備註吧,和上面的modify函式一模一樣~~

long long query(long long rt, long long l, long long r, long long s, long long t)

long long mid = (l + r) >> 1;

pushdown(rt, l, r);//把lazy標誌下推,否則查詢出來的資訊是錯的(畢竟不是所有的數都在一整個區間裡,可能跨區間)

if(t <= mid)else if(s > mid)//同區間修改

else return query(rt << 1, l, mid, s, t) + query(rt << 1 | 1, mid + 1, r, s, t);//此處是區間求和,求區間最大值只要改成max就好了

}

這是個人認為線段樹里最噁心的一種操作,即讓乙個區間全都乘上乙個數,一開始想不過如此的操作,看起來和加法沒什麼區別,但是仔細一想不要忘了乙個細節:線段樹並不會更新到葉子節點,那麼就要考慮一下演算法的順序了,要是加法標記沒有推下去而直接算乘法,顯然是錯解。

這樣想,好像並不好操作,那簡化一下,運用乘法分配率即\((a+lazy[b])*lazy_[c]=a*lazy_[c]+lazy[b]*lazy_[c]\),說白了就是先乘後加,再向下推lazy標記的時候就不會出現如上的錯解。

上**:

#includeconst int maxn = 1e5+5;

long long tree[maxn << 2],lazy[maxn << 2], lazy_[maxn << 2],x[maxn];//線段樹不要忘記要左移兩位,陣列別開小了

int n,m,order,add,mod;

using namespace std;

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

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

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

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

tree[rt] = (tree[rt << 1] + tree[rt << 1 | 1])%mod;

}void update(int rt, int l, int r, long long w, long long cheng)

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

void modify(int rt, int l, int r, int s, int t, long long w, long long cheng)

pushdown(rt,l,r);//lazy標記下推

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

if(mid >= s) modify(rt << 1, l, mid, s, t, w, cheng);

if(mid < t) modify(rt << 1 | 1, mid + 1, r, s, t, w, cheng);

tree[rt] = (tree[rt << 1] + tree[rt << 1 | 1])%mod;

}long long query(int rt, int l, int r, int s, int t)

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

pushdown(rt, l, r);

long long ans = 0;

if(mid >= s) ans = (ans+query(rt << 1, l, mid, s, t)) % mod;

if(mid < t) ans = (ans + query(rt << 1 | 1, mid + 1, r, s, t)) % mod;

return ans%mod;

}void solve()else if(order == 1)

else }}

int main()

線段樹的總結就先寫這麼多了,以後發現好題還會繼續更新~

線段樹總結

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

線段樹總結

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

線段樹總結

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