1. 程式人生 > >主席樹(可持久化線段樹)講解 [POJ 2104] K-th Number

主席樹(可持久化線段樹)講解 [POJ 2104] K-th Number

題目大意:本題包含多組資料。每組資料都會給你一個數組,包含 n 個數;一共有 m 個詢問,每次詢問輸入三個整數 L , R , k,表示求區間 [ L , R ] 以內第 k 小的數。( 1 ≤ n ≤ 100 000 , 1 ≤ m ≤ 5 000 , 陣列中每個數的絕對值 ≤ 109 )

知識講解:
在講這道題之前,我想先講講本人對主席樹的一些看法。
主席樹,又被稱作“可持久化線段樹”,是 OI 上一個相當重要的資料結構。它可以解決例如查詢歷史版本的區間值,區間第 k 大/小值,以及區間內不同數的個數,等等。

既然要實現可持久化,那麼在建樹過程中,我們就需要將歷史資訊給記錄下來。首先,我們最容易想到的就是:每發生一次修改過程,就建立一棵新樹。但是,建立一棵新樹的消耗非常大,如果要建立 m 棵新樹,複雜度將為 m

nlogn,無法承受。考慮到每次修改最多隻會讓這個數以及它的所有父節點發生變化,因此我們可以在修改的時候僅對這個點到根節點的數進行修改。此時建立 m 棵新樹的複雜度即為 mlogn,節約了不少時間和空間。

那麼對於每個新建的樹,我們要維護它的建造時間;根據根節點的性質,每次修改會伴隨著一些數的變化,而根節點總是會隨著變化,所以根節點順便也維護了建造時間。(對於區間第 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; }