P3834 【模板】可持久化線段樹 1(主席樹)
題目背景
這是個非常經典的主席樹入門題——靜態區間第K小
數據已經過加強,請使用主席樹。同時請註意常數優化
題目描述
如題,給定N個正整數構成的序列,將對於指定的閉區間查詢其區間內的第K小值。
輸入輸出格式
輸入格式:第一行包含兩個正整數N、M,分別表示序列的長度和查詢的個數。
第二行包含N個正整數,表示這個序列各項的數字。
接下來M行每行包含三個整數 l,r,k, 表示查詢區間 [l,r]內的第k小值。
輸出格式:輸出包含k行,每行1個正整數,依次表示每一次查詢的結果
輸入輸出樣例
輸入樣例#1:5 5 25957 6405 15770 26287 26465 2 2 1 3 4 1 4 5 1 1 2 2 4 4 1輸出樣例#1:
6405
15770
26287
25957
26287
說明
數據範圍:
對於20%的數據滿足: 1≤N,M≤10
對於50%的數據滿足: 1≤N,M≤103
對於80%的數據滿足: 1≤N,M≤105
對於100%的數據滿足: 1≤N,M≤2⋅105
對於數列中的所有數 ai ,均滿足 −109≤ai≤109
樣例數據說明:
N=5,數列長度為5,數列從第一項開始依次為 [25957,6405,15770,26287,26465]
第一次查詢為 [2,2] 區間內的第一小值,即為6405
第二次查詢為 [3,4]區間內的第一小值,即為15770
第三次查詢為 [4,5]區間內的第一小值,即為26287
第四次查詢為 [1,2]區間內的第二小值,即為25957
第五次查詢為 [4,4] 區間內的第一小值,即為26287
Solution:
補坑了——很久很久以前的老坑。(差點沒想起自己還有這個模板沒寫博客`~`)
主席樹類似於權值線段樹(或者說就是多棵權值線段樹和起來,因為以前介紹過權值線段樹,所以這裏概念不講了~)。
對於整個區間的第$k$大查詢,一個很簡單的思路就是對原序列離散,然後維護一顆權值線段樹,每次查詢子樹中的出現的數值個數,和$k$比對遞歸到葉子節點就好了($sum[rt]>=k$查左子樹$sum[rt<<1]$與$k$比對,$sum[rt]<k$查右子樹$sum[rt<<1|1]$比對$k-sum[rt<<1]$)。
那麽推及任意一段區間,由於權值線段樹只能維護一整段的區間,所以我們可以利用前綴和的思想,維護$n$棵權值線段樹(第$i$棵維護區間$[1,i]$),那麽每次查詢時我們直接用$sum[r]-sum[l-1]$就是區間$[l,r]$的子樹的數值出現的個數。
思路是很簡單,但是我們發現這樣寫需要建$n$棵線段樹,$n^2$的空間顯然開不下。
於是,神犇$HJT$(野史:稱之“主席”,因為其名簡寫和某位國家前領導人一樣),發明了$nlogn$的建樹方法。
我們發現從已經建好的$[1,i]$區間的樹,推及到$[1,i+1]$建樹時,改變的只是$[1,i]$區間所建的權值線段樹從根節點到某一葉子節點的路徑上的節點信息,而其它節點都沒有被改變。
那麽就好搞了,記錄每棵樹的根節點,然後每新建一棵樹(等價於在前面的線段樹中使某個數的值加$1$),若當前節點信息不需要修改則直接從當前根節點向上一棵樹的該節點連邊,若被修改了,則新建一個節點保存被修改的信息。
最後就是比較簡單的查詢了。(唯一需要註意的是,主席樹維護的是權值線段樹,所以每個點上的權值需要離散,我的離散過程是直接模擬,略醜)。
代碼:
1 #include<bits/stdc++.h> 2 #define il inline 3 #define ll long long 4 #define lson l,m,rt<<1 5 #define rson m+1,r,rt<<1|1 6 #define For(i,a,b) for(int (i)=(a);(i)<=(b);(i)++) 7 using namespace std; 8 const int N=200005; 9 int n,m,rt[N],rank[N],cnt,w[N],tot; 10 11 struct num{ 12 int v,id; 13 bool operator<(const num a)const{return v==a.v?id<a.id:v<a.v;} 14 }a[N]; 15 16 struct node{ 17 int ls,rs,sum; 18 }t[N*40]; 19 20 il int gi(){ 21 int a=0;char x=getchar();bool f=0; 22 while((x<‘0‘||x>‘9‘)&&x!=‘-‘)x=getchar(); 23 if(x==‘-‘)x=getchar(),f=1; 24 while(x>=‘0‘&&x<=‘9‘)a=(a<<3)+(a<<1)+x-48,x=getchar(); 25 return f?-a:a; 26 } 27 28 il void build(int l,int r,int &rt){ 29 rt=++tot; 30 t[rt].sum=0; 31 if(l==r)return; 32 int m=l+r>>1; 33 build(l,m,t[rt].ls); 34 build(m+1,r,t[rt].rs); 35 } 36 37 il void update(int l,int r,int c,int lst,int &rt){ 38 rt=++tot; 39 t[rt].ls=t[lst].ls; 40 t[rt].rs=t[lst].rs; 41 t[rt].sum=t[lst].sum+1; 42 if(l==r)return; 43 int m=l+r>>1; 44 if(c<=m)update(l,m,c,t[lst].ls,t[rt].ls); 45 else update(m+1,r,c,t[lst].rs,t[rt].rs); 46 } 47 48 il int query(int L,int R,int l,int r,int k){ 49 if(l==r)return l; 50 int m=l+r>>1; 51 int ret=t[t[R].ls].sum-t[t[L].ls].sum; 52 if(k<=ret)return query(t[L].ls,t[R].ls,l,m,k); 53 else return query(t[L].rs,t[R].rs,m+1,r,k-ret); 54 } 55 56 int main(){ 57 n=gi(),m=gi(); 58 For(i,1,n)a[i].v=gi(),a[i].id=i; 59 sort(a+1,a+n+1); 60 rank[a[1].id]=++cnt;w[cnt]=a[1].v; 61 For(i,2,n) 62 if(a[i].v!=a[i-1].v)w[++cnt]=a[i].v,rank[a[i].id]=cnt; 63 else rank[a[i].id]=cnt; 64 build(1,cnt,rt[0]); 65 For(i,1,n)update(1,cnt,rank[i],rt[i-1],rt[i]); 66 int x,y,z; 67 while(m--){ 68 x=gi(),y=gi(),z=gi(); 69 int ans=query(rt[x-1],rt[y],1,cnt,z); 70 printf("%d\n",w[ans]); 71 } 72 return 0; 73 }
P3834 【模板】可持久化線段樹 1(主席樹)