怎麼說呢,其實從入坑線段樹一來,經歷過兩個階段,第一個階段是初學階段,那個時候看網上的一些教學博文和模板入門了線段樹,html
而後挑選了一個線段樹模板做爲本身的模板,通過了一點本身的修改,而後就已知用着,其實對線段樹理解不深,屬於就會套個模板的狀態,期間有人問我線段樹的問題,我其實也半知不解的,node
後來,刷了幾道DFS序+線段樹的題目,那個時候多多少少有所長進,再次回過頭來從新看線段樹的代碼,理解有所加深,算是勉強理清了線段樹這個東西,c++
再到如今,前不久剛把splay搞完,對平衡二叉搜索樹有了更加深的理解,並且線段樹相比splay,仍是比較簡單的,因此終於下定決心,好好整理一下,把線段樹這一塊理清晰理透徹。算法
一開始我沒有把這種線段樹考慮進來……由於比較簡單,有lazy的線段樹纔是好線段樹!數組
模板能夠參見:計蒜客 30996 - Lpl and Energy-saving Lamps函數
先是最基礎的區間修改,區間求和線段樹模板:優化
#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)); } } } }
線段樹的原理其實很簡單,總結來講有下面幾個要點:ui
區間修改,區間最小值線段樹模板: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; }
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
顧名思義,就是能夠同時完成以下操做的線段樹:
具體代碼直接看題目:Luogu 3373
同時還有進階版:HDU 4578
求多個矩形的面積並:HDU 1542
求長方體3次及以上體積交:HDU 3642
主要體現了線段樹可以適用的場合之普遍。