線段樹(Segment Tree)總結

0 寫在前面

怎麼說呢,其實從入坑線段樹一來,經歷過兩個階段,第一個階段是初學階段,那個時候看網上的一些教學博文和模板入門了線段樹,html

而後挑選了一個線段樹模板做爲本身的模板,通過了一點本身的修改,而後就已知用着,其實對線段樹理解不深,屬於就會套個模板的狀態,期間有人問我線段樹的問題,我其實也半知不解的,node

後來,刷了幾道DFS序+線段樹的題目,那個時候多多少少有所長進,再次回過頭來從新看線段樹的代碼,理解有所加深,算是勉強理清了線段樹這個東西,c++

再到如今,前不久剛把splay搞完,對平衡二叉搜索樹有了更加深的理解,並且線段樹相比splay,仍是比較簡單的,因此終於下定決心,好好整理一下,把線段樹這一塊理清晰理透徹。算法


1 線段樹模板

 

2.0 單點修改,區間查詢線段樹

一開始我沒有把這種線段樹考慮進來……由於比較簡單,有lazy的線段樹纔是好線段樹!數組

模板能夠參見:計蒜客 30996 - Lpl and Energy-saving Lamps函數

 

2.1 區間修改,區間求和線段樹模板

先是最基礎的區間修改,區間求和線段樹模板:優化

#include<bits/stdc++.h>
using namespace std; const int maxn=50000+10; int n,a[maxn]; /********************************* Segment Tree - st *********************************/
struct Node{ int l,r; int val,lazy; void update(int x) { val+=(r-l+1)*x; lazy+=x; } }node[4*maxn]; void pushdown(int root) { if(node[root].lazy) { node[root*2].update(node[root].lazy); node[root*2+1].update(node[root].lazy); node[root].lazy=0; } } void pushup(int root) { node[root].val=node[root*2].val+node[root*2+1].val; } void build(int root,int l,int r) //對區間[l,r]建樹
{ node[root].l=l; node[root].r=r; node[root].val=0; node[root].lazy=0; if(l==r) node[root].val=a[l]; else { int mid=l+(r-l)/2; build(root*2,l,mid); build(root*2+1,mid+1,r); pushup(root); } } void update(int root,int st,int ed,int val) //區間[st,ed]所有加上val
{ if(st>node[root].r || ed<node[root].l) return; if(st<=node[root].l && node[root].r<=ed) node[root].update(val); else { pushdown(root); update(root*2,st,ed,val); update(root*2+1,st,ed,val); pushup(root); } } int query(int root,int st,int ed) //查詢區間[st,ed]的和
{ if(st>node[root].r || ed<node[root].l) return 0; if(st<=node[root].l && node[root].r<=ed) return node[root].val; else { pushdown(root); int ls=query(root*2,st,ed); int rs=query(root*2+1,st,ed); pushup(root); return ls+rs; } } /********************************* Segment Tree - ed *********************************/

int main() { int T; cin>>T; for(int kase=1;kase<=T;kase++) { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); build(1,1,n); printf("Case %d:\n",kase); char op[8]; while(1) { scanf("%s",op); if(op[0]=='E') break; if(op[0]=='A') { int p,x; scanf("%d%d",&p,&x); update(1,p,p,x); } if(op[0]=='S') { int p,x; scanf("%d%d",&p,&x); update(1,p,p,-x); } if(op[0]=='Q') { int l,r; scanf("%d%d",&l,&r); printf("%d\n",query(1,l,r)); } } } }

 

2.2 原理要點總結

線段樹的原理其實很簡單,總結來講有下面幾個要點:ui

  1. 把二叉樹的節點按從上到下、從左到右存在一個數組裏的話,對於每一個節點x,它與左右兒子的關係是:左兒子 ls = 2*x,右兒子 rs = 2*x + 1;
  2. 線段樹每一個節點存儲的值是由左右兒子節點的值O(1)獲得的;
  3. 每一次區間更新,只對 屬於該區間的 又是最靠上層的 的節點進行操做,這個操做有兩部分(Node結構體中的成員函數update):①修改本節點的值,②給本節點打上lazy標記;
  4. lazy標記:某個節點若是打着lazy標記,代表它的兒子們尚未更新;
  5. 每訪問到一個節點(無論是更新的訪問仍是查詢的訪問),若是要繼續深刻到其兒子們,顯然就要先把lazy標記push下去,一旦push下去就要記得再push上來;

 

2.3 區間修改,區間最值線段樹模板

區間修改,區間最小值線段樹模板:spa

#include<bits/stdc++.h>
using namespace std; const int maxn=50000+10; const int INF=0x3f3f3f3f; int n,a[maxn]; /********************************* Segment Tree - st *********************************/
struct Node{ int l,r; int val,lazy; void update(int x) { val+=x; lazy+=x; } }node[4*maxn]; void pushdown(int root) { if(node[root].lazy) { node[root*2].update(node[root].lazy); node[root*2+1].update(node[root].lazy); node[root].lazy=0; } } void pushup(int root) { node[root].val=min(node[root*2].val,node[root*2+1].val); } void build(int root,int l,int r) //對區間[l,r]建樹
{ node[root].l=l; node[root].r=r; node[root].val=0; node[root].lazy=0; if(l==r) node[root].val=a[l]; else { int mid=l+(r-l)/2; build(root*2,l,mid); build(root*2+1,mid+1,r); pushup(root); } } void update(int root,int st,int ed,int val) //區間[st,ed]所有加上val
{ if(st>node[root].r || ed<node[root].l) return; if(st<=node[root].l && node[root].r<=ed) node[root].update(val); else { pushdown(root); update(root*2,st,ed,val); update(root*2+1,st,ed,val); pushup(root); } } int query(int root,int st,int ed) //查詢區間[st,ed]的最小值
{ if(st>node[root].r || ed<node[root].l) return INF; if(st<=node[root].l && node[root].r<=ed) return node[root].val; else { pushdown(root); int ls=query(root*2,st,ed); int rs=query(root*2+1,st,ed); pushup(root); return min(ls,rs); } } /********************************* Segment Tree - ed *********************************/

int main() { memset(a,0,sizeof(a)); n=10; build(1,1,n); update(1,5,10,2); for(int i=1;i<=n;i++) cout<<query(1,i,i)<<" "; cout<<endl; cout<<query(1,1,n)<<endl; update(1,1,5,-2); for(int i=1;i<=n;i++) cout<<query(1,i,i)<<" "; cout<<endl; cout<<query(1,1,n)<<endl; }

 

區間修改,區間最大值線段樹模板:code

#include<bits/stdc++.h>
using namespace std; const int maxn=50000+10; const int INF=0x3f3f3f3f; int n,a[maxn]; /********************************* Segment Tree - st *********************************/
struct Node{ int l,r; int val,lazy; void update(int x) { val+=x; lazy+=x; } }node[4*maxn]; void pushdown(int root) { if(node[root].lazy) { node[root*2].update(node[root].lazy); node[root*2+1].update(node[root].lazy); node[root].lazy=0; } } void pushup(int root) { node[root].val=max(node[root*2].val,node[root*2+1].val); } void build(int root,int l,int r) //對區間[l,r]建樹
{ node[root].l=l; node[root].r=r; node[root].val=0; node[root].lazy=0; if(l==r) node[root].val=a[l]; else { int mid=l+(r-l)/2; build(root*2,l,mid); build(root*2+1,mid+1,r); pushup(root); } } void update(int root,int st,int ed,int val) //區間[st,ed]所有加上val
{ if(st>node[root].r || ed<node[root].l) return; if(st<=node[root].l && node[root].r<=ed) node[root].update(val); else { pushdown(root); update(root*2,st,ed,val); update(root*2+1,st,ed,val); pushup(root); } } int query(int root,int st,int ed) //查詢區間[st,ed]的最大值
{ if(st>node[root].r || ed<node[root].l) return -INF; if(st<=node[root].l && node[root].r<=ed) return node[root].val; else { pushdown(root); int ls=query(root*2,st,ed); int rs=query(root*2+1,st,ed); pushup(root); return max(ls,rs); } } /********************************* Segment Tree - ed *********************************/

int main() { memset(a,0,sizeof(a)); n=10; build(1,1,n); update(1,5,10,2); for(int i=1;i<=n;i++) cout<<query(1,i,i)<<" "; cout<<endl; cout<<query(1,1,n)<<endl; update(1,1,5,-2); for(int i=1;i<=n;i++) cout<<query(1,i,i)<<" "; cout<<endl; cout<<query(1,1,5)<<endl; cout<<query(1,1,n)<<endl; }

2 線段樹的變化與應用

 

2.1 線段樹+DFS序

DFS序:

首先對一棵樹進行先序遍歷,產生一個序列,用一個數組 in[1~n] 存儲每一個節點在序列裏的位置,顯然樹根是第一個,因此 in[root] = 1;

同時,因爲DFS有回溯存在,因此訪問完一個節點的全部子節點(直接的或者間接的),會回到當前節點 x,假設回到當前節點前最後一個訪問的節點是 y,咱們令 out[x] = in[y];

簡單的來講,一棵樹進行先序遍歷產生一個序列,一個節點 x 其統領的整棵子樹在序列上會是一整段區間,而 in[x] 和 out[x] 就是該區間的左右端點。

 

而線段樹配合DFS序,用處就是將「子樹修改,子樹查詢」變成「區間修改,區間查詢」,具體請看下面兩篇博文:

HDU 5692 - Snacks:http://www.cnblogs.com/dilthey/p/8988368.html

CodeForces 838B - Diverging Directions:http://www.cnblogs.com/dilthey/p/9005129.html

 

2.2 加乘線段樹

顧名思義,就是能夠同時完成以下操做的線段樹:

  1. 某區間全部數所有加上某個數;
  2. 某區間全部數所有乘上某個數;
  3. 查詢某區間全部數之和;

具體代碼直接看題目:Luogu 3373

同時還有進階版:HDU 4578

 

2.3 線段樹+掃描線

求多個矩形的面積並:HDU 1542

求長方體3次及以上體積交:HDU 3642

 

2.4 線段樹優化Dijkstra算法

主要體現了線段樹可以適用的場合之普遍。

詳見:單源最短路進階 - 「舊王已死,新王當立!」 —— 線段樹優化Dijkstra算法

相關文章
相關標籤/搜索