不了解線段樹的朋友可以看看我上一篇博客: [線段樹系列] 普通線段樹
補充一個上一篇漏了的點:線段樹要開4倍空間。
然而面對龐大的數據我們開maxn<<2的空間是肯定開不下的。
這時候就要用到動態開點線段樹來節省空間了。( 或者離散化 )
動態開點線段樹大概長這樣( 又是隨手畫的一個圖 ):
理解一下就好,你寫出來的也不長這樣

圖中紅色水彩線圈出來的就是你的動態開點線段樹在某時間大概長的樣子。
簡單來說就是,你要用到一個點才開那個點,不用的點不開,可以大幅節省空間。
這樣空間復雜度可以大致降到O(nlogn)。
是不是很棒。
接下來是實現:
一開始,你只有一個根節點。
通過update函數往樹里面插點,開兩個數組記錄每個節點的左右兒子編號。
遞歸進入左右兒子,如果要用新點,就開新點。
上代碼(以區間和為例):
插入
inline void update(int &o,int l,int r,int x,int val){ if(!o)o=++ncnt; if(l==r){ sum[o]+=val;return; } int mid=(l+r)>>1; if(x<=mid)update(lc[o],l,mid,x,val); else update(rc[o],mid+1,r,x,val); pushup(o); }
查詢
int ask(int o,int l,int r,int L,int R){ if(!o)return 0; if(L<=l && R>=r)return sum[o]; int val=0; int mid=(l+r)>>1; if(L<=mid)val+=ask(lc[o],l,mid,L,R); if(R>mid)val+=ask(rc[o],mid+1,r,L,R); return val; }
其它操作跟線段樹是一樣的,你只要把普通線段樹里的p<<1換成lc[p],p<<1|1換成rc[p]就行了。
我上一篇的線段樹也是記錄了左右兒子編號的,其實沒有必要,只是為了這一篇做個鋪墊。
靈活運用動態開點線段樹可以節省很多內存,而且能做到普通線段樹做不到的事情。
比如題目要求在線操作不能離散化,值域又特別大:inf,並且詢問q不大
這時候我們就可以用動態開點線段樹開qloginf個點過掉這題。
是不是很美妙。
上代碼:
#include<bits/stdc++.h>
#define LOG 20
using namespace std;const int maxn=100010; int rt,ncnt,lc[maxn*LOG],rc[maxn*LOG],sum[maxn*LOG]; inline void pushup(int o){ sum[o]=sum[lc[o]]+sum[rc[o]];//更新
} inline void update(int &o,int l,int r,int x,int val){ if(!o)o=++ncnt;//開點
if(l==r){ sum[o]+=val;return; } int mid=(l+r)>>1; if(x<=mid)update(lc[o],l,mid,x,val); else update(rc[o],mid+1,r,x,val); pushup(o); } int ask(int o,int l,int r,int L,int R){ if(!o)return 0;//沒這個點,直接返回0
if(L<=l && R>=r)return sum[o]; int val=0; int mid=(l+r)>>1; if(L<=mid)val+=ask(lc[o],l,mid,L,R); if(R>mid)val+=ask(rc[o],mid+1,r,L,R);//遞歸計算
return val; } int main(){ int n;cin>>n; for(int i=1;i<=n;i++){ int num;cin>>num; update(rt,1,n,i,num); } int q;cin>>q; while(q--){ int l,r;cin>>l>>r; cout<<ask(rt,1,n,l,r)<<endl; } }
這個東西不難,我就不多說了,下一篇更新可持久化線段樹( 主席樹 )。
撰文不易,希望能幫到各位。本系列持續更新,求頂
