線段樹主要用於區間記錄信息(如區間和、最大最小值等),首先是建樹:
這里以求和為例:
1 const int MAXM=50000; //定義 MAXM 為線段最大長度 2
3 int a[MAXM+5],st[(MAXM<<2)+5]; // a 數組為 main 函數中讀入的內容,st 數組為需要查詢的數的信息(如和、最值等),樹的空間大小為線段最大長度的四倍 4
5 void build(int o,int l,int r){ //傳入的參數為 o:當前需要建立的結點;l:當前需要建立的左端點;r:當前需要建立的右端點 6 if(l==r)st[o]=a[l]; //當左端點等於右端點即建立葉子結點時,直接給數組信息賦值 7 else{ 8 int m=l+((r-l)>>1); // m 為中間點,左兒子結點為 [l,m] ,右兒子結點為 [m+1,r]; 9 build(o<<1,l,m); //構建左兒子結點 10 build((o<<1)|1,m+1,r); //構建右兒子結點 11 st[o]=st[o<<1]+st[(o<<1)|1]; //遞歸返回時用兒子結點更新父節點,此處可進行更新最大值、最小值、區間和等操作 12 } 13 } 14
15 { //在 main 函數中的語句 16 build(1,1,n); 17 }
然后是比較簡單的單點修改以及區間查詢操作:
單點修改:
1 void update(int o,int l,int r,int ind,int ans){ //o、l、r為當前更新到的結點、左右端點,ind為需要修改的葉子結點左端點,ans為需要修改成的值; 2 if(l==r){ //若當前更新點的左右端點相等即到葉子結點時,直接更新信息並返回 3 st[o]=ans; 4 return; 5 } 6 int m=l+((r-l)>>1); 7 if(ind<=m){ //若需要更新的葉子結點在當前結點的左兒子結點的范圍內,則遞歸更新左兒子結點,否則更新右兒子結點 8 update(o<<1,l,m,ind,ans); 9 } 10 else{ 11 update((o<<1)|1,m+1,r,ind,ans); 12 } 13 st[o]=max(st[o<<1],st[(o<<1)|1]);//遞歸回之后用兒子結點更新父節點(此處是區間最大值) 14 } 15
16 { //在main函數中的語句 17 update(1,1,n,ind,ans); 18 }
對應單點修改的區間查詢:
1 int query(int o,int l,int r,int ql,int qr){ //ql、qr為需要查詢的區間左右端點 2 if(ql>r||qr<l) return -1; //若當前結點和需要查找的區間不相交,則返回一個對於區間查詢無關的值(如求和時返回0,求最大值時返回-1等) 3 if(ql<=l&&qr>=r) return st[o]; //若當前結點的區間被需要查詢的區間覆蓋,則返回當前結點的信息 4 int m=l+((r-l)>>1); 5 int p1=query(o<<1,l,m,ql,qr),p2=query((o<<1)|1,m+1,r,ql,qr); //p1為查詢左兒子結點得到的信息,p2為查詢右兒子結點得到的信息 6 return max(p1,p2); //綜合兩個兒子結點的信息並返回 7 } 8
9 { //main函數中的語句 10 printf("%d\n",query(1,1,n,a,b)); 11 }
然后是線段數的區間修改以及相應的查詢:
區間修改用到了lazy的思想,即當一個區間需要更新時,只遞歸更新到那一層結點,並將其下層結點所需要更新的信息保存在數組中,然后返回,只有當下次遍歷到那個結點(更新過程中或查詢過程中),才將那個結點的修改信息傳遞下去,這樣就避免了區間修改的每個值的修改
區間修改(包括區間加值和區間賦值)及相應查詢:
區間加值:
1 void pushup(int o){ //pushup函數,該函數本身是將當前結點用左右子節點的信息更新,此處求區間和,用於update中將結點信息傳遞完返回后更新父節點
2 st[o]=st[o<<1]+st[o<<1|1]; 3 } 4
5 void pushdown(int o,int l,int r){ //pushdown函數,將o結點的信息傳遞到左右子節點上
6 if(add[o]){ //當父節點有更新信息時才向下傳遞信息
7 add[o<<1]+=add[o]; //左右兒子結點均加上父節點的更新值
8 add[o<<1|1]+=add[o]; 9 int m=l+((r-l)>>1); 10 st[o<<1]+=add[o]*(m-l+1); //左右兒子結點均按照需要加的值總和更新結點信息
11 st[o<<1|1]+=add[o]*(r-m); 12 add[o]=0; //信息傳遞完之后就可以將父節點的更新信息刪除
13 } 14 } 15
16 void update(int o,int l,int r,int ql,int qr,int addv){ //ql、qr為需要更新的區間左右端點,addv為需要增加的值
17 if(ql<=l&&qr>=r){ //與單點更新一樣,當當前結點被需要更新的區間覆蓋時
18 add[o]+=addv; //更新該結點的所需更新信息
19 st[o]+=addv*(r-l+1); //更新該結點信息
20 return; //根據lazy思想,由於不需要遍歷到下層結點,因此不需要繼續向下更新,直接返回
21 } 22
23 pushdown(o,l,r); //將當前結點的所需更新信息傳遞到下一層(其左右兒子結點)
24 int m=l+((r-l)>>1); 25 if(ql<=m)update(o<<1,l,m,ql,qr,addv); //當需更新區間在當前結點的左兒子結點內,則更新左兒子結點
26 if(qr>=m+1)update(o<<1|1,m+1,r,ql,qr,addv); //當需更新區間在當前結點的右兒子結點內,則更新右兒子結點
27 pushup(o); //遞歸回上層時一步一步更新回父節點
28 } 29
30 ll query(int o,int l,int r,int ql,int qr){ //ql、qr為需要查詢的區間
31 if(ql<=l&&qr>=r) return st[o]; //若當前結點覆蓋區間即為需要查詢的區間,則直接返回當前結點的信息
32 pushdown(o,l,r); //將當前結點的更新信息傳遞給其左右子節點
33 int m=l+((r-l)>>1); 34 ll ans=0; //所需查詢的結果
35 if(ql<=m)ans+=query(o<<1,l,m,ql,qr); //若所需查詢的區間與當前結點的左子節點有交集,則結果加上查詢其左子節點的結果
36 if(qr>=m+1)ans+=query(o<<1|1,m+1,r,ql,qr); //若所需查詢的區間與當前結點的右子節點有交集,則結果加上查詢其右子節點的結果
37 return ans; 38 }
區間改值(其實只有pushdow函數和update中修改部分與區間加值不同):
1 void pushup(int o){ 2 st[o]=st[o<<1]+st[o<<1|1]; 3 } 4
5 void pushdown(int o,int l,int r){ //pushdown和區間加值不同,改值時修改結點信息只需要對修改后的信息求和即可,不用加上原信息
6 if(change[o]){ 7 int c=change[o]; 8 change[o<<1]=c; 9 change[o<<1|1]=c; 10 int m=l+((r-l)>>1); 11 st[o<<1]=(m-l+1)*c; 12 st[o<<1|1]=(r-m)*c; 13 change[o]=0; 14 } 15 } 16
17 void update(int o,int l,int r,int ql,int qr,int c){ 18 if(ql<=l&&qr>=r){ //同樣更新結點信息和區間加值不同
19 change[o]=c; 20 st[o]=(r-l+1)*c; 21 return; 22 } 23
24 pushdown(o,l,r); 25 int m=l+((r-l)>>1); 26 if(ql<=m)update(o<<1,l,m,ql,qr,c); 27 if(qr>=m+1)update(o<<1|1,m+1,r,ql,qr,c); 28 pushup(o); 29 } 30
31 int query(int o,int l,int r,int ql,int qr){ 32 if(ql<=l&&qr>=r) return st[o]; 33 pushdown(o,l,r); 34 int m=l+((r-l)>>1); 35 int ans=0; 36 if(ql<=m)ans+=query(o<<1,l,m,ql,qr); 37 if(qr>=m+1)ans+=query(o<<1|1,m+1,r,ql,qr); 38 return ans; 39 }
