主席樹(可持久化線段樹)講解 [POJ 2104] K-th Number
題目大意:本題包含多組資料。每組資料都會給你一個數組,包含 n 個數;一共有 m 個詢問,每次詢問輸入三個整數 L , R , k,表示求區間 [ L , R ] 以內第 k 小的數。( 1 ≤ n ≤ 100 000 , 1 ≤ m ≤ 5 000 , 陣列中每個數的絕對值 ≤
知識講解:
在講這道題之前,我想先講講本人對主席樹的一些看法。
主席樹,又被稱作“可持久化線段樹”,是 OI 上一個相當重要的資料結構。它可以解決例如查詢歷史版本的區間值,區間第 k 大/小值,以及區間內不同數的個數,等等。
既然要實現可持久化,那麼在建樹過程中,我們就需要將歷史資訊給記錄下來。首先,我們最容易想到的就是:每發生一次修改過程,就建立一棵新樹。但是,建立一棵新樹的消耗非常大,如果要建立 m 棵新樹,複雜度將為
那麼對於每個新建的樹,我們要維護它的建造時間;根據根節點的性質,每次修改會伴隨著一些數的變化,而根節點總是會隨著變化,所以根節點順便也維護了建造時間。(對於區間第 k 小問題,我們只需要將根節點的建造時間為 R 的樹和根節點為 L-1 的樹相減即可得到這個區間的變化情況,根據它我們就可以求出這個問題的答案)
建樹的時候,如果用的是指標,那麼尤其需要注意的就是指標是否指向 NULL。因為建樹過程中,稍微一不注意就會讓指標越界,這種情況在最開始除錯的時候極難被發現。所以我們在建樹時,最好多加上幾個判斷 NULL 的條件,以擴音交的時候頻繁 RE 或 WA。
題目分析:(簡略)
通過上面的分析,這道題的方向就很清楚了:一道直觀的求區間第 k 小的數問題。
首先我們所有的數進行離散化,方便儲存。之後對於離散化之後的原陣列,將裡面每一個數對應地新增至主席樹的節點內。最後判斷時如同上面講的一樣。
下面附上程式碼:
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int MX = 1000005;
struct Node{
int val;
Node *ls,*rs;
Node(){
ls = rs = NULL;val = 0;
}
};
Node node[MX*2],*tail = node,*root[MX];
int lob[MX],line[MX],ori[MX],n,m,now = 0;
Node *setup(int lf,int rt){
Node *nd = ++tail;
if (lf == rt){
nd->val = 0;
nd->ls = nd;
nd->rs = nd;
} else {
int mid = (lf + rt) / 2;
nd->ls = setup(lf,mid);
nd->rs = setup(mid + 1,rt);
nd->val = 0;
}
return nd;
}
Node *update(Node *prev,int lf,int rt,int val){
Node *nd = ++tail;
if (lf == rt){
if (prev != NULL)
nd->val = prev->val + 1;
else
nd->val++;
nd->ls = nd;
nd->rs = nd;
} else {
int mid = (lf + rt) / 2;
if (val <= mid){
nd->ls = update(prev->ls,lf,mid,val);
nd->rs = prev->rs;
nd->val = nd->ls->val + prev->rs->val;
} else {
nd->ls = prev->ls;
nd->rs = update(prev->rs,mid + 1,rt,val);
nd->val = prev->ls->val + nd->rs->val;
}
}
return nd;
}
int query(Node *left,Node *right,int lf,int rt,int val){ //一定要多加幾個判 NULL
if (lf == rt)
return lf;
int mid = (lf + rt) / 2 , res;
if (left->ls != NULL && right->ls != NULL)
res = right->ls->val - left->ls->val;
else
res = right->val - left->val;
if (val <= res){
if (left->ls != NULL && right->ls != NULL)
return query(left->ls,right->ls,lf,mid,val);
return query(left,right,lf,mid,val);
}
else{
if (left->rs != NULL && right->rs != NULL)
return query(left->rs,right->rs,mid + 1,rt,val - res);
return query(left,right,mid + 1,rt,val);
}
}
int main(){
scanf("%d%d",&n,&m);
int p,q,kth;
for (int i = 1;i <= n;i++){
scanf("%d",&ori[i]);
line[i] = lob[i] = ori[i];
}
sort(lob + 1,lob + n + 1);
int size = unique(lob + 1,lob + n + 1) - (lob + 1);
root[0] = setup(1,size);
for (int i = 1;i <= n;i++){
line[i] = lower_bound(lob + 1,lob + size + 1,line[i]) - lob;
root[i] = update(root[i - 1],1,size,line[i]);
}
for (int i = 1;i <= m;i++){
scanf("%d%d%d",&p,&q,&kth);
int tmp = query(root[p - 1],root[q],1,size,kth);
printf("%d\n",lob[tmp]);
}
return 0;
}