題解 P3834 【【模板】可持久化線段樹 1(主席樹)】

 

洛谷模板題鏈接

 

大佬博客

 

複雜度分析:離散化數組O(nlogn),構建基礎線段樹O(nlogn),統計並插入線段樹(O(longn)+O(longn))=O(nlogn),詢問的複雜度爲O(mlogn),複雜度總和O((m+n)logn)

 

代碼:

#include <bits/stdc++.h>
#define MAX 200010

using namespace std;

int nodeNum;
//所有節點的數量
int L[MAX<<5],R[MAX<<5],sum[MAX<<5];
//L[i]表示編號爲i的節點的左兒子的編號
//sum[i]表示編號爲i的節點所代表的區間內數字出現的次數
int a[MAX],Hash[MAX];
//a[i]爲原數組 Hash[i]爲排序後數組
int T[MAX];
//T[i]爲插入i個點後的樹的根節點編號
int read()
{
    int ans=0,flag=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
            flag=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        ans=(ans<<3)+(ans<<1)+ch-'0';
        ch=getchar();
    }
    return ans*flag;
}
int build(int l,int r) //建一個空樹(所有sum[i]都爲0)
{
    int num=++nodeNum; //num爲當前節點編號
    if(l!=r)
    {
        int m=(l+r)>>1;
        L[num]=build(l,m);
        R[num]=build(m+1,r);
    }
    return num; //返回當前節點的編號
}
int update(int pre,int l,int r,int x) //pre爲舊樹該位置節點的編號
{
    int num=++nodeNum; //新建節點的編號
    L[num]=L[pre];
    R[num]=R[pre];
    sum[num]=sum[pre]+1;
    //該節點左右兒子初始化爲舊樹該位置節點的左右兒子
    //因爲插入的a[i](或Hash[x])在該節點所代表的區間中 所以sum++
    if(l!=r)
    {
        int m=(l+r)>>1;
        if(x<=m)
            L[num]=update(L[pre],l,m,x);
        //x出現在左子樹 因此右子樹保持與舊樹相同 修改左子樹
        else
            R[num]=update(R[pre],m+1,r,x);
    }
    return num;
}
int query(int u,int v,int l,int r,int k) //第k小
{
    if(l==r)
        return Hash[l]; //找到第k小 l是節點編號 所以答案是Hash[l]
    int m=(l+r)>>1;
    int num=sum[L[v]]-sum[L[u]];
    //用第一次模擬 這樣比較容易看得懂 此時u=l-1 v=r
    //則num= (1~r)樹的左節點數字出現的次數 - (1~(l-1))樹的左節點數字出現的次數
    //即num等於([l,r])樹左兒子數字出現的次數
    if(num>=k)
        return query(L[u],L[v],l,m,k);
    //當 左兒子數字出現的次數大於等於k 時 意味着 第k小的數字在左子樹處
    else
        return query(R[u],R[v],m+1,r,k-num);
    //否則去右子樹處找第k-num小的數字
}

int main()
{
    int n=read(),m=read();
    for(int i=1; i<=n; i++)
    {
        a[i]=read();
        Hash[i]=a[i];
    }
    sort(Hash+1,Hash+1+n);
    int size=unique(Hash+1,Hash+1+n)-Hash-1;
    //size爲線段樹維護的數組的大小==Hash數組中不重複的數字的個數
    T[0]=build(1,size); //初始化 建立一顆空樹 並把該樹的根節點的編號賦值給T[0]
    for(int i=1; i<=n; i++)
    {
        int x=lower_bound(Hash+1,Hash+1+size,a[i])-Hash;
        //在Hash的 [1,size+1)--->[1,size] 中二分查找第一個
        // 大於等於(在這裏可以看成等於) a[i]的Hash[x]
        T[i]=update(T[i-1],1,size,x);
        //更新a[i]帶來的影響
        //並將新樹的根節點的編號賦值給T[i]
    }
    while(m--)
    {
        int l=read(),r=read(),k=read();
        printf("%d\n",query(T[l-1],T[r],1,size,k)); //因爲a[l]有影響 所以是T[l-1]
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章