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

主席樹學了幾天纔算初步瞭解了,我先講一下無修改即靜態的主席樹,之後在講帶修改即動態的主席樹。主席樹比較抽象,感覺很難沒有圖只靠文字理解。
接下來進入正題

參考博客:http://www.cnblogs.com/zyf0163/p/4749042.html

主席樹

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

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

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

比如有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,這裏主席樹最精妙的地方就出來了。我們建立的這些線段樹的結構,維護的區間是相同的,主席樹充分利用了這些線段樹中的相同部分,大大減少了空間消耗,達到優化目的。

直接上圖,邊看圖邊理解上面的話。
這裏寫圖片描述

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

建完主席樹後我們看看它是怎麼查找區間[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+(前綴和更新)nlog2n
一般我們數組大小就開nlog2n (動態不一樣,之後會講)
時間複雜度:mlog2n

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[maxn*20],R[maxn*20],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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章