更新地址:傳送門
---
權值線段樹
所謂權值線段樹,就是一種維護值而非下標的線段樹,我個人傾向於稱呼它為值域線段樹。
舉個栗子:對於一個給定的數組,普通線段樹可以維護某個子數組中數的和,而權值線段樹可以維護某個區間內數組元素出現的次數。
在實現上,由於值域范圍通常較大,權值線段樹會采用離散化或動態開點的策略優化空間。
更新操作:
更新的時候,我們向線段樹中插入一個值v,那么所有包含v的區間值都需要+1。(每個節點維護對應區間中出現了多少個數)
int update (long long v,long long l,long long r,int pos) { // 插入v,當前區間為[l,r]。 if (!pos) pos=++tot_node; // 如果該節點不存在,則新建節點。 if (l<=v&&v<=r) { // 如果當前區間包含插入值。 tree[pos].val++; // 出現次數+1。 if (l==r) return pos; // 如果遞歸到葉子節點,退出。 } long long mid=(l+r)>>1; if (v<=mid) tree[pos].ls=update(v,l,mid,tree[pos].ls); else tree[pos].rs=update(v,mid+1,r,tree[pos].rs); // 判斷插入值是在當前區間的哪一半。 pushup(pos); // 回溯。 return pos; }
查詢操作:
查詢操作類似二叉樹。
1 long long query (long long l,long long r,long long L,long long R,int pos) { // 查詢區間[L,R]中數字的數量,當前區間為[l,r]。
2 if (!pos) return 0;
3 // 如果該節點不存在,必然沒有到達過。
4 if (L<=l&&r<=R) { 5 // 如果當前區間屬於查詢區間。 6 return tree[pos].val; 7 // 直接返回區間中數字的數量。 8 } 9 long long mid=(l+r)>>1,ans=0; 10 if (L<=mid) ans+=query(l,mid,L,R,tree[pos].ls); 11 if (mid<R) ans+=query(mid+1,r,L,R,tree[pos].rs); 12 // 統計區間和。 13 return ans; 14 }
練習題:
作為練習模板,可以考慮逆序對。大體思路是每次查詢a[i]+1~n的元素個數。
線段樹合並
所謂線段樹合並,就是通過將合並兩顆線段樹獲得信息,其正確性由線段樹的穩定結構保證。
線段樹合並通常是一個自底向上的過程,在深搜的途中將子節點的樹合並到父節點上,從而實現對父節點值的統計。
線段樹合並的復雜度是$nlogn$,比啟發式合並少一個$logn$。
不難發現,如果按照線段樹合並的原始思想直接在每一個需要遍歷的節點上都單獨建立一個線段樹肯定會爆空間。在這里可以使用被稱為“回收內存”的方法:由於在合並之后子節點的信息已經歸入父節點,所以子節點沒有用處,那么可以將其所有節點回收丟入一個內存池,往后更新的時候可以從內存池里取節點而非新建節點。
回收內存:
1 inline int newId () { 2 if (pool_top) return mempool[pool_top--]; 3 return ++tot_node; 4 } 5 inline void killId (int &x) { 6 mempool[++pool_top]=x; 7 tree[x].ls=tree[x].rs=tree[x].val=0; 8 x=0; 9 }
合並操作:
合並操作與左偏樹的合並有一點像,就是遞歸合並每一個節點。
1 int merge (int l,int r,int x,int y) { 2 if (!x||!y) return x+y; 3 int now=newId(),mid=(l+r)>>1; 4 if (l==r) { 5 tree[now].val=tree[x].val+tree[y].val; 6 } else { 7 tree[now].ls=merge(l,mid,tree[x].ls,tree[y].ls); 8 tree[now].rs=merge(mid+1,r,tree[x].rs,tree[y].rs); 9 tree[now].val=tree[tree[now].ls].val+tree[tree[now].rs].val; 10 } 11 killId(x),killId(y); 12 return now; 13 }
練習題:
可以考慮做一下Tree Rotation,大致題意是給一棵二叉樹,可以交換每個點的左右子樹,要求前序遍歷葉子的逆序對最少。
由於左右兒子的交換不會影響更上層的值,所以在每次合並的時候直接統計即可。