可持久化线段树

发布时间 2023-03-23 17:04:00作者: 风月无边~windymoon

可持久化线段树

可持久化线段树最大的特点是:可以访问历史版本。

考虑一棵普通的线段树,进行单点修改,每次修改会改变 \(\log n\)个点。

于是每次修改的时候,不修改原来的节点,而是在它旁边新建一个节点,把原来节点的信息(如左右儿子编号、区间和等)复制到新节点上,并对新节点进行修改。

对于查询历史版本,只需记录每一次修改对应的新根节点编号,每次询问从对应的根节点往下查询就可以了。

所以我们只要在记录左右儿子的基础上,保存插入每个数的时候的根节点就可以实现持久化了。

空间 \(O((n+m)\log n)\),一般开 N<<5

主席树

主席树全称是可持久化权值线段树,主要用来求区间第 \(k\)

主要思想是每个位置都维护一个线段树,线段树的节点是值的范围,然后第 \(i\) 个线段树中某个区间 \([x, y]\) 维护的是 \(1 \sim i\) 位置上的数字在 \([x, y]\) 范围内的个数。

利用前缀和的思想,如果需要得到 \([l,r]\) 的统计信息,只需要用 \([1,r]\) 的信息减去 \([1,l - 1]\) 的信息就行了,即第 \(r\) 棵线段树减去第 \(l-1\) 棵线段树。

\(insert\)

il void insert(int &rt,cs int l,cs int r,cs int k){
    t[++id]=t[rt],rt=id,t[rt].sum++;
    if(l>=r) return;
    int mid=(l+r)>>1;
    if(k<=mid) insert(t[rt].ls,l,mid,k);
    else insert(t[rt].rs,mid+1,r,k);
    return;        
}

\(ask\)

il int ask(int ql,int qr,int l,int r,int k){
    if(l==r) return l;
    int mid=(l+r)>>1,lsz=t[t[qr].ls].sum-t[t[ql].ls].sum;
    if(lsz>=k) return ask(t[ql].ls,t[qr].ls,l,mid,k);
    else return ask(t[ql].rs,t[qr].rs,mid+1,r,k-lsz);
}
code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;

namespace Q{
    il int rd(){
        ri int x=0;ri bool f=0;ri char c=getchar();
        while(!isdigit(c)) f|=(c=='-'),c=getchar();
        while(isdigit(c))x=x*10+(c^48),c=getchar();
        return f?-x:x;
    }
    il void wt(int x){
        if(x<0) x=-x,putchar('-');
        if(x>=10) wt(x/10);
        return putchar(x%10+48),void();
    }
} using namespace Q;

cs int N=2e5+5,inf=0x3f3f3f3f;
int n,m,rot[N],arr[N],b[N];

struct qaq{
    int num,id;
    bool operator<(cs qaq o)cs{
        return num<o.num;
    }
}a[N];

namespace Tree{
    int id;
    struct qwq{
        int ls,rs,sum;
    }t[N<<5];
    il void insert(int &rt,cs int l,cs int r,cs int k){
        t[++id]=t[rt],rt=id,t[rt].sum++;
        if(l>=r) return;
        int mid=(l+r)>>1;
        if(k<=mid) insert(t[rt].ls,l,mid,k);
        else insert(t[rt].rs,mid+1,r,k);
        return;        
    }
    il int ask(int ql,int qr,int l,int r,int k){
        if(l==r) return l;
        int mid=(l+r)>>1,lsz=t[t[qr].ls].sum-t[t[ql].ls].sum;
        if(lsz>=k) return ask(t[ql].ls,t[qr].ls,l,mid,k);
        else return ask(t[ql].rs,t[qr].rs,mid+1,r,k-lsz);
    }
} using namespace Tree;


signed main(){
    n=rd(),m=rd();
    for(ri int i=1;i<=n;++i) a[i]={rd(),i};
    sort(a+1,a+1+n);
    int len=0;a[0].num=inf;
    for(ri int i=1;i<=n;++i){
        if(a[i].num!=a[i-1].num){
            arr[++len]=a[i].num;
        } 
        b[a[i].id]=len;
    }
    for(ri int i=1;i<=n;++i){
        rot[i]=rot[i-1];
        insert(rot[i],1,len,b[i]);
    }
    for(ri int i=1,l,r,k;i<=m;++i){
        l=rd(),r=rd(),k=rd();
        wt(arr[ask(rot[l-1],rot[r],1,len,k)]);
        putchar('\n');
    }
    return 0;
}

edit