1. 程式人生 > >主席樹(靜態) 圖文講解讓你一次就懂 hdu2665為例

主席樹(靜態) 圖文講解讓你一次就懂 hdu2665為例

主席樹 先介紹一下主席樹,主席樹也稱函式式線段樹也稱可持久化線段樹。(其實就是支援查詢歷史版本,這個在看完之後就會了解)

其實主席樹就是很多線段樹組合的總體,從它的其它稱呼也可以看出來了,其實它本質上還是線段樹。

主席樹就是利用函數語言程式設計的思想來使線段樹支援詢問歷史版本、同時充分利用它們之間的共同資料來減少時間和空間消耗的增強版的線段樹。那麼它是怎麼實現的呢?

比如有4個數5 3 6 9,求區間[2,4]第2小的數。

T[i]表示第i棵線段樹的根節點編號,L[i]表示節點i的左子節點編號,R[i]表示節點i的右子節點編號,sum[i]表示節點i對應區間中數的個數。 我們先把序列離散化後是2 1 3 4。 我之前已經說了,主席樹就是很多線段樹的總體,而這些線段樹就是按給定序列的所有字首建立的。從T[0]開始建立空樹,之後依次加入第i個數建立T[i]。 注意,如果我們直接以序列的所有字首建立線段樹肯定會MLE,這裡主席樹最精妙的地方就出來了。我們建立的這些線段樹的結構,維護的區間是相同的,主席樹充分利用了這些線段樹中的相同部分,大大減少了空間消耗,達到優化目的。

直接上圖,邊看圖邊理解上面的話。

圖中上面為用序列所有字首建立的線段樹,下面為所有線段樹組合成主席樹。 圖中每個節點上面為節點編號,節點下面為對應區間,節點中數為區間中含有的數的個數,後面省略了區間。 從圖中應該可以看出主席樹是怎麼充分利用這些線段樹的相同結構來減少空間消耗的。當要新建一個線段樹時最多隻需要新增log2nlog2n個節點,相當於只更新了一條鏈,其它節點與它的前一個線段樹公用。

建完主席樹後我們看看它是怎麼查詢區間[2,4]第2小的數的。 首先我們要了解這些線段樹是可加減的,比如我們要處理區間[l,r],那麼我們只需處理sum[T[r]]-sum[T[l-1]]就是給定序列的區間[l,r]中的數的個數。因為我們是按字首處理的,這裡看圖自己體會一下。 這裡我們要先計算res=sum[L[T[4]]]-sum[L[T[1]]]=1,即算出給定序列區間[2,4]中數的範圍在區間[1,2]的數的個數,如果它的值大於k那麼我們就應該從線段樹的根節點走到左節點找第k個數,否則我們就應該從根節點到右節點找第k-res個數,之後遞迴下去直到葉子節點,返回葉子節點對應區間即為我們查詢的數在離散化後序列中的下標。這裡返回值為3,對應離散化後序列中數3,即原序列中數6。

講到這裡,靜態的主席樹就講完了。我們算算時空複雜度。 設原序列有n個數,含有m次詢問 空間複雜度:(建空樹)4*n+(字首和更新)nlog2nlog2n 一般我們陣列大小就開nlog2nlog2n (動態不一樣,之後會講) 時間複雜度:mlog2nlog2n hdu2665 題意 別被它的題目描述騙了,這題其實就是主席樹求靜態區間第k小的裸題。序列n範圍<=1e5,詢問m範圍<=1e5。

題解 是裸題,直接按上面講的寫即可,具體可看程式碼註釋。

#include <bits/stdc++.h> using namespace std; typedef long long ll;

const int maxn = 1e5+5; int T[maxn],L[maxn20],R[maxn20],sum[maxn*20]; //sz[]為原序列,h[]為離散化後序列 int sz[maxn],h[maxn]; int n,q,ql,qr,k,tot;

void build(int& rt,int l,int r) //建空樹 直播系統開發找上海捌躍網路科技有限公司 { rt = ++tot; sum[rt] = 0; if(l==r) return; int mid = (l+r)>>1; build(L[rt],l,mid); build(R[rt],mid+1,r); }

//對所有字首更新樹 void update(int& rt,int l,int r,int pre,int x) { rt = ++tot; L[rt]=L[pre]; R[rt]=R[pre]; sum[rt] = sum[pre]+1; if(l==r) return; int mid = (l+r)>>1; if(x<=mid) update(L[rt],l,mid,L[pre],x); else update(R[rt],mid+1,r,R[pre],x); }

int query(int s,int e,int l,int r,int k) { if(l==r) return l; int mid = (l+r)>>1; int res = sum[L[e]]-sum[L[s]]; //左子節點數的個數 if(k<=res) return query(L[s],L[e],l,mid,k); else return query(R[s],R[e],mid+1,r,k-res); }

int main() { int t; scanf("%d",&t); while(t–) { scanf("%d%d",&n,&q); for(int i=1;i<=n;i++) scanf("%d",sz+i),h[i]=sz[i]; sort(h+1,h+1+n); int num = unique(h+1,h+1+n)-h-1; tot=0; build(T[0],1,num); for(int i=1;i<=n;i++) { //離散化後更新 update(T[i],1,num,T[i-1],lower_bound(h+1,h+1+num,sz[i])-h); } while(q–) { scanf("%d%d%d",&ql,&qr,&k); int ans = query(T[ql-1],T[qr],1,num,k); printf("%d\n",h[ans]); } } return 0; }