初识可持久化线段树和主席树

初识可持久化线段树和主席树

可持久化线段树

什么是可持久化线段树

​ 来看的朋友,线段树一定已经烂熟与心。可持久化线段树就是在线段树的基础上做一些改变从而使线段树具有查询修改前的版本的能力。举个栗子。

给你一段有N个数的数列,有M个操作,可以询问经过X个修改后l~r区间和。

先考虑普通线段树。为此,我们会建立M个线段树,这需要的空间是​,在许多题目中,如此大的空间往往是不允许的。那么该如何处理呢。这里需要我们的可持久化线段树来完成啦!

观察一下线段树修改的过程,其实只有​的点被修改了,修改的只是从根到点的那一段路径。由此我们想到了优化的方法。我们在每次修改时,不直接修改原本的点,而直接复制一份修改的路径,在这套路径上修改,然后再将这条路径上的每个节点连到原来的其他节点上,让新建的树链并入原线段树中构成新的线段树,这样省去了​的空间。

例题:题目传送门

#include<cstdio>
#define maxn 30000010
using namespace std;
int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0' || c>'9'){
        if(c=='-') f=-1;
        c=getchar();
    }
    while(c>='0' && c<='9')
        x=x*10+c-'0',c=getchar();
    return x*f;
}
int n,m,cnt;
int tr[maxn],ll[maxn],rr[maxn],rt[maxn];
int build(int l,int r){
    int root=++cnt;
    if(l==r){
        tr[root]=read();
        return root;
    }
    int mid=(l+r)/2;
    ll[root]=build(l,mid),rr[root]=build(mid+1,r);
    return root;
}
int update(int pre,int l,int r,int a,int b){
    int root=++cnt;//动态开点,防止报内存,不用去记录多余的空间
    if(l==r){
        tr[root]=b;
        return root;
    }
    ll[root]=ll[pre],rr[root]=rr[pre];//新节点想原来节点的左右儿子分别建一条边
    int mid=(l+r)/2;
    if(a<=mid) ll[root]=update(ll[pre],l,mid,a,b);//更改左或右儿子为新建树链节点的儿子
    else rr[root]=update(rr[pre],mid+1,r,a,b);
    return root;
}
int fnd(int pre,int l,int r,int x){//线段树的常规操作
    if(l==r) return tr[pre];
    int mid=(l+r)/2;
    if(x<=mid) return fnd(ll[pre],l,mid,x);
    else return fnd(rr[pre],mid+1,r,x);
}
int main(){
    n=read(),m=read();
    build(1,n);
    rt[0]=1;
    for(int i=1;i<=m;i++){
        int x=read(),bo=read();
        if(bo==1){
            int a=read(),b=read();
            rt[i]=update(rt[x],1,n,a,b);
        }
        if(bo==2){
            int a=read();
            rt[i]=rt[x];
            printf("%d\n",fnd(rt[x],1,n,a));
        }
    }
    return 0;
}

主席树

主席树也是一棵可持久化线段树,常用的操作为求区间第K大。

注意:主席树是一棵值域线段树,看起来和平衡树很想。先考虑值域线段树,求整个区间的第K大?当然,我们把数据先离散化一边,在按期大小插入线段树中,比线段树区间的中值小,则往左边搜,反之向右边搜。这样第K大点就是在该点左边有k个数,简直和平衡树一模一样有木有!

主席树求的时区间第K大,相当于我们没1~i-1建立线段树,这就和可持久化挂边了。主要操作没什么,和可持久化线段树操作没什么区别

例题:题目传送门

#include<cstdio>
#define maxn 30000010
using namespace std;
int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0' || c>'9'){
        if(c=='-') f=-1;
        c=getchar();
    }
    while(c>='0' && c<='9')
        x=x*10+c-'0',c=getchar();
    return x*f;
}
int n,m,cnt;
int tr[maxn],ll[maxn],rr[maxn],rt[maxn];
int build(int l,int r){
    int root=++cnt;
    if(l==r){
        tr[root]=read();
        return root;
    }
    int mid=(l+r)/2;
    ll[root]=build(l,mid),rr[root]=build(mid+1,r);
    return root;
}
int update(int pre,int l,int r,int a,int b){
    int root=++cnt;
    if(l==r){
        tr[root]=b;
        return root;
    }
    ll[root]=ll[pre],rr[root]=rr[pre];
    int mid=(l+r)/2;
    if(a<=mid) ll[root]=update(ll[pre],l,mid,a,b);
    else rr[root]=update(rr[pre],mid+1,r,a,b);
    return root;
}
int fnd(int pre,int l,int r,int x){
    if(l==r) return tr[pre];
    int mid=(l+r)/2;
    if(x<=mid) return fnd(ll[pre],l,mid,x);
    else return fnd(rr[pre],mid+1,r,x);
}
int main(){
    n=read(),m=read();
    build(1,n);
    rt[0]=1;
    for(int i=1;i<=m;i++){
        int x=read(),bo=read();
        if(bo==1){
            int a=read(),b=read();
            rt[i]=update(rt[x],1,n,a,b);
        }
        if(bo==2){
            int a=read();
            rt[i]=rt[x];
            printf("%d\n",fnd(rt[x],1,n,a));
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值