GPS北斗衛星校時伺服器的關鍵技術和特點
前置知識
二叉樹
定義
基本操作複雜度: $ O(logn) $
線段樹是一棵二叉樹,每個節點表示序列上的一段區間,其中根節點表示區間[1,n]。
從根節點開始,只要區間長度不為1,就將區間劃分為兩半,並分給兩個子節點。
若當前節點表示區間[l,r],
當l≠r時,左孩子表示[l,(l+r)/2],右孩子表示[(l+r)/2+1,r];
當l=r時,該節點為葉子節點。
基本性質
性質1
線段樹的總節點數為2n-1。
性質2
線段樹的層數約為 $ log_{ 2 } n $ ,即每個葉節點到根節點的距離約為 \(log_{2}n\) 。
性質3
任何區間都可以用不超過 \(2log_{2}n\)
編號
線段樹的編號方式通常有兩種:即2i和2i+1作i的孩子或按構建順序編號。
儲存
線段樹的儲存也有兩種方式:多個數組或struct結構體。
以struct結構體陣列為例:
(lc,rc分別為左右孩子的編號,sum為該區間的總和。
陣列大小為序列長度兩倍。
至於區間的左右端點[l,r],可以儲存,也可以遞迴時計算。
若需要節省空間,則不儲存。)
構建
構建過程遞迴實現,
void build(int u,int l,int r)
(u為當前節點編號,l,r為區間端點)
從根節點開始構建,遞迴時計算並代入l,r,若l=r,該點為葉子節點,令sum=序列原值,否則,遞迴構建左右孩子,之後令sum=sum左孩子+sum右孩子。
在主程式中:
t=1;
build(1,1,n);
即可。
應用
單點修改
修改過程遞迴實現,
void update(int u,int l,int r,int x,int w)
(u當前節點編號,l和r為區間端點,x為要修改的位置,w為變化量)
從根節點開始修改,若當前點為葉子節點,使sum+=w並返回
否則判斷x在左孩子還是右孩子,遞迴進行修改,之後令sum=左孩子sum+右孩子sum。
在主程式中呼叫:
update(1,1,n,x,y);
區間和查詢
查詢過程遞迴實現,
void query(int u,int l,int r,int ll,int rr)
(u為當前節點,l和r是區間端點,ll和rr分別為所查詢區間的左右端點)
需要用外層定義變數ans儲存答案。
從根節點開始,首先判斷查詢區間與節點區間是否完全重合,若重合,ans+=sum並返回,否則判斷查詢區間是否完全在左孩子或者右孩子,若是則遞迴查詢。否則將查詢區間拆分,兩側分別遞迴查詢。
在主程式中:
ans=0;
query(1,1,n,x,y);
維護區間最大值/最小值
區間修改
我們線上段樹的每個節點上再儲存一個資訊,稱為“標記”,記為tag,表示該節點所包含的每個位置,都要再加上tag。
首先,像區間詢問一樣將區間對應到儘量少的節點上,
令它們
sum+=(r-l+1)*w;//增加的區間和
tag+=w;//增加標記
然後將sum資訊向上pushup。
區間修改後的單點/區間查詢
查詢與之前類似,
查詢過程遞迴實現,
void query(int u,int l,int r,int ll,int rr,int w)
但由於標記的出現,多了“下傳標記”一步。
下傳標記時,孩子的標記要疊加。
類似於pushup,標記下傳的過程寫入一個
void pushdown(int u,int l,int r)
注
一個節點上的tag一定已經計入自身的sum。
在修改和查詢時,只要需要進入子節點,就一定要pushdown。
注意
當維護資訊較多時,為簡化程式碼,通常將類似於”a[u].sum=a[a[u].lc].sum+a[a[u].rc].sum”的上傳資訊的步驟寫入void pushup(int u)並呼叫pushup(u);
並非原創,僅是整理,請見諒