前置知識:權值線段樹。html
主席樹也就是可持久化線段樹,它能夠幹嗎呢?咱們看這樣一道題目。node
給定N個正整數構成的序列,將對於指定的閉區間查詢其區間內的第K小值。
數據範圍:\(1≤N,M≤2⋅10^5,-10^9≤a_i≤10^9\)
咱們都知道權值線段樹能夠求全局第K大,可是不能求區間第K大,那遇到區間第K大如何處理呢?
區間?差分?對,咱們能夠用差分,權值線段樹差分。
假設有這樣一組數據
4 5
6 2 19 8
2 2 1
3 4 1
3 4 2
1 2 2
4 4 1
咱們先離散,這是權值線段樹基本操做把數列變爲2,1,4,3看到有重複的也要去重。咱們看圖怎麼實現:這是在線開點的線段樹,因此兒子序號並不必定是父親節點序號2倍或2倍+1
這是離散事後的權值線段樹(空樹),紅色數字表明這區間有幾個數。
咱們開始加樹進去,第一個數6,離散後是2,就把全部包含2的區間 個數+1,變成這樣一張圖。
再加入2,離散後1,把全部包含1的區間個數+1.如圖:
再加入19,離散後是4,把全部包含4的區間個數+1,如圖:
再加入8,離散後是3,把全部包含3的區間個數+1,如圖:
建完樹了,咱們每次開一顆線段樹都記錄下來,因此點的序號並非我圖中的序號*。這樣咱們獲得了5顆線段樹,假設看第一個詢問2,2,1,咱們只須要用第2顆線段樹減去第1顆線段樹這樣就能夠獲得區間[2,2]的值分佈狀況接下來,就能夠用權值線段樹的方法,求區間第K大了。
可是咱們能夠發現,要建n顆線段樹,那麼空間複雜度變成\(n^2\),炸穿,須要優化。
容易發現,每次加入一個點,發現只會更改線段樹上一條路上的值,其餘的咱們能夠鏈上之前的點。空間複雜度\(nlogn\)。完美。ios
#include<iostream> #include<algorithm> #include<cstdio> #include<cstdlib> using namespace std; typedef long long ll; ll a[2001000],hash[2010010],tot,root[2010010]; //root[],存下每一顆線段樹的根 struct TREE { ll ln,rn,zhi; }t[10010100];//ln左兒子,rn右兒子,zhi表明有多少個點在這個區間 void gai(ll &node,ll last,ll l,ll r,ll x) { node=++tot;//在線開點 t[node]=t[last]; t[node].zhi++; if(l==r) return; ll mid=(l+r)/2; if(x<=mid) gai(t[node].ln,t[last].ln,l,mid,x); else gai(t[node].rn,t[last].rn,mid+1,r,x); } ll cha(ll node,ll last,ll l,ll r,ll k) { if(l==r) return a[l]; ll p=t[t[node].ln].zhi-t[t[last].ln].zhi;//差分 ll mid=(l+r)/2; if(k<=p) return cha(t[node].ln,t[last].ln,l,mid,k); else return cha(t[node].rn,t[last].rn,mid+1,r,k-p); } int main() { ll n,m,x,y,k; cin>>n>>m; for(ll i=1;i<=n;i++) scanf("%lld",&a[i]),hash[i]=a[i]; sort(a+1,a+1+n); ll tt=unique(a+1,a+1+n)-a-1;//排序後,去重 for(ll i=1;i<=n;i++) { hash[i]=lower_bound(a+1,a+1+tt,hash[i])-a;//二分找出,這個點離散後的值。 gai(root[i],root[i-1],1,tt,hash[i]);//根據上一次獲得的線段樹,修改值。 } for(ll i=1;i<=m;i++) { scanf("%lld%lld%lld",&x,&y,&k); printf("%lld\n",cha(root[y],root[x-1],1,tt,k));//差分 } }
其實主席樹能夠帶修改,詳細看個人另外一篇博客。
題目連接
可持久化線段樹(主席樹模板)
可憐的狗狗
Count on a tree
Query on a tree III優化