資料結構——線段樹
線段樹是一種基於分治思想的類似於二叉樹的資料結構,一般用於陣列的資訊統計,相比於樹狀陣列,線段樹有著更廣闊的應用空間,但是相對的其程式碼量長,且常數大
一.
首先我們來講線段樹的建樹過程,請看下圖:
這張圖就是線段樹的儲存結構,我們從最長的區間開始依次分成兩部分,每一部分都有一個需要維護的權,建樹過程比較簡單,程式碼如下:
inline void build(int l,int r,int rt) //l表示當前的左端點,r表示右端點,rt是當前區間的編號 { if(l == r) //當左右端點相同,說明當前的區間是一個點,給予該點一個初值 { scanf("%d",c[rt]); return ; } int m = (l + r) >> 1; build(l,m,rt<<1); build(m + 1,r,rt<<1|1); //分成兩個區間繼續進行建樹操作 update(rt); //更新當前區間的值 return ; } build(1,n,1);
關於上面的update函式,一般是根據你需要維護的條件進行的,下面給出幾個例子:
inline void update(int rt) { c[rt] = c[rt<<1] + c[rt<<1|1]; //維護區間和 c[rt] = std::max(c[rt<<1],c[rt<<1|1]);//維護區間最大值 c[rt] = std::min(c[rt<<1],c[rt<<1|1]);//維護區間最小值 return ; }
現在建樹過程大概都瞭解了,我們開始講查詢和修改操作
首先我們先講單點修改,具體操作就是從根節點開始,每次比較我們插入的位置與中點位置的大小,從而選擇左右區間,做法類似於二分,在這裡不多講,程式碼如下:
inline void modify(intl,int r,int rt,int x,int y) //給x加上y { if(l == r) { c[rt] += y; return ; } int m = (l + r) >> 1; if(x <= m) modify(l,m,rt<<1,x,y); else modify(m + 1,r,rt<<1|1,x,y); update(rt); return ; } modify(1,n,1,x,y);
單點查詢操作的實現在做法上與單點修改一樣,程式碼如下:
inline void query(int l,int r,int rt,int p) //p為我們需要查詢的點 { if(l == r && l == p) { ans = c[rt]; return ; } int m = (l + r) >> 1; if(p <= m) query(l,m,rt<<1,p); else query(m + 1,r,rt<<1|1,p); return ; } query(1,n,1,p);
然後我們考慮區間查詢操作,我們可以發現,當我們要查詢的區間的左端點小於等於中點時,那麼它與左區間一定有交集,我們就可以進入左子樹查詢,相應的,當它的右端點大於中點時,與右區間有交集,我們進入右子樹查詢,並且當我們當前的區間左端點大於等於查詢區間左端點,且右端點小於等於查詢區間右端點時,那麼這個區間一定在查詢區間內,我們就直接用該區間權值維護答案,不必再向下搜尋,於是我們得到區間查詢的方法,程式碼如下:
inline void query(int l,int r,int rt,int nl,int nr) //nl,nr為當前需要查詢的區間 { if(nl <= l &&r <= nr) { ans += c[rt]; //這裡我們用求區間和舉例 return ; } int m = (l + r) >> 1; if(nl <= m) query(l,m,rt<<1,nl,nr); if(nr > m) query(m + 1,r,rt<<1|1,nl,nr); return ; } query(1,n,1,nl,nr);
二.
關於區間修改,一個顯然的想法是,我們像區間查詢那樣做,但是在這裡有個問題,我們在修改區間的時候,如果整個區間都被覆蓋,那麼每一個節點都需要更新,複雜度會上升到O(n),因此,我們來考慮如何進行優化,在這裡,我們就引入延遲標記,當一個區間l ~ r被修改後,我們需在[l ~ r]這個區間加上一個 延遲標記,當我們在查詢的時候,如果當前區間的若干個子樹需要修改或查詢,我們就檢驗當前區間是否有延遲標記,然後下發標記,這時我們發現,區間修改的複雜度會降至O(nlogn)
下面給出區間修改和加法延遲標記的實現(這裡以區間查詢為例):
inline void sign(int l,int r,int rt,int v) //延遲標記 { c[rt] += (r - l + 1)*v; sg[rt] += v; return ; } inline void push_down(int l,int r,int rt) //下放標記 { if(sg[rt]) { int m = (l + r) >> 1; sign(l,m,sg[rt]); sign(m + 1,r,sg[rt]); sg[rt] = 0; } return ; } inline void modify(int l,int r,int rt,int nl,int nr,int v) { if(nl <= l && r <= nr) { sign(l,r,rt,v); return ; } push_down(l,r,rt); int m = (l + r) >> 1; if(nl <= m) modify(l,m,rt<<1,nl,nr,v); if(nr > m) query(m + 1,r,rt<<1|1,nl,nr,v); return ; } inline void query(int l,int r,int rt,int nl,int nr) { if(nl<=l&&r<=nr) { ans += c[rt]; return ; } push_down(l,r,rt); int m = (l + r) >> 1; if(nl <= m) query(l,m,rt<<1,nl,nr); if(nr > m) query(m + 1,r,rt<<1|1,nl,nr); return ; } modify(1,n,1,nl,nr,v); query(1,n,1,nl,nr);
值得注意的是,在複雜的線段樹操作中可能會出現多種延遲標記,這時需要我們考慮運算順序,如一個線段樹中同時存在著加法標記和乘法標記,運算的順序不同,結果就可能會不同
關於延遲標記這裡給出兩道例題:
這兩道例題都非常的簡單,在這裡我給出第一個例題的程式碼,第二個例題可以參照第一題的做法
#include <cstdio> #include <cstring> #include <algorithm> #define root 1,n,1 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define mid (l+r)>>1 const int maxn = 1e6 + 6; int n,tm; long long c[maxn]; int sg[maxn]; long long ans; inline int read() { char ch = getchar();int x = 0,f = 1; while(ch<'0'||ch>'9'){if(ch == '-') f = -1;ch = getchar();} while(ch>='0'&&ch<='9'){x = x*10 + ch -'0';ch = getchar();} return x*f; } inline void update(int rt) { c[rt] = c[rt<<1] + c[rt<<1|1]; return ; } inline void sign(int l,int r,int rt,int v) { c[rt] += 1ll*(r-l+1)*v; sg[rt] += v; return ; } inline void push_down(int l,int r,int rt) { if(sg[rt]) { int m = mid; sign(lson,sg[rt]); sign(rson,sg[rt]); sg[rt] = 0; } return ; } inline void build (int l,int r,int rt) { if(l==r) { c[rt] = read(); return ; } int m = mid; build(lson); build(rson); update(rt); } inline void modify(int l,int r,int rt,int nl,int nr,int v) { if(nl<=l&&r<=nr) { sign(l,r,rt,v); return ; } push_down(l,r,rt); int m = mid; if(nl<=m) modify(lson,nl,nr,v); if(nr>m) modify(rson,nl,nr,v); update(rt); } inline void query(int l,int r,int rt,int nl,int nr) { if(nl<=l&&r<=nr) { ans += c[rt]; return ; } push_down(l,r,rt); int m = mid; if(nl<=m) query(lson,nl,nr); if(nr>m) query(rson,nl,nr); update(rt); } int main(int argc, char const *argv[]) { n = read(); tm = read(); build(root); for(int i = 1;i <= tm;i ++) { int flag,x,y,k; flag = read(); if(flag == 1) { x = read(); y = read(); k = read(); modify(root,x,y,k); } else { ans = 0; x = read(); y = read(); query(root,x,y); printf("%lld\n",ans); } } return 0; }