可持久化线段树解释(又叫主席树,可持久化数组)洛谷3919 3402

因为我是菜鸡,如果有说错的话,恳请大佬们指出 QAQ

本文分为以下几部分:

一:名称的由来

二:原理解释

三:代码怎么写

四:例题

名称的由来

首先,可持久化线段树,分为:可持久化+线段树。

可持久化就是历史信息回顾的意思。比如每次操作我都让某个数改变,那么我想回顾第k次操作,第i个是什么数应该怎么做呢?naive的做法就是我每改变一个数都复制一次这个数组就可以了,但是我们发现其实很多数字相对上一次来说都是没有变化的,所以盲目的拷贝是不是一种无用功呢?这里我们通过可持久化就可以解决这种盲目的拷贝,可以证明每次数据的改变的复杂度为O(logn),其中n为数列长度。

线段树,这个emm...... 您都看到可持久化线段树了,应该线段树知道是什么了吧。线段树是一种区间分块的技术比如根节点保存的范围是[1,n]那么左子节点的范围时[1,n/2],右子节点的范围是[n/2+1,n];大概就是这个意思。然后一直递归到叶子节点。

另外,为什么叫主席树呢?因为发明这个数据结构的人叫做hjt,和某某主席的拼音一样咯(以上为瞎bb

原理解释

首先我们需要建树,树能够存放它的左儿子和右儿子的标号。这个标号是我们可持久化的前提。(注意上图的标号和下面的例程运行结果的标号有出入,但是不影响理解)

那么每次更新到来时,我们认为有一个新版本来了,这时候我们新建一个头节点,这个头节点很机智,对于不在我们更新范围的节点,我们可以直接连过去,就是这个操作,使得我们不需要复制一串的数!注意,头节点是我们操作的核心,这个是控制版本的核心。

根据下图来理解:

比如现在我想更新[4:4]这个节点,我们可以这样:

其中绿色是我们新加的本次版本信息带来的节点,我们看到8连向了2!这种指向前驱使得我们不需要存储[1:2]的信息。而多出来的节点10就是我们更改的[4:4]多出来的一个点。

所以,当我们需要回滚版本,比如回到第几次操作之后时候,我们只需要找到对应的头节点即可。比如这里第一次的头节点是1,第二次的头节点是8.

到这里,原理就解释完了。是不是很简单呢?至于可持久化并查集,本质上就是多了个并查集,但是需要启发式合并+去掉路径压缩。

代码:

int  build(int l,int r){
    ts++;
    int pos=ts;
    if(r-l==0){
        st[pos].v=arr[l];
        return pos;
    }
    int mid=l+(r-l)/2;
    st[pos].ls=build(l,mid);
    st[pos].rs=build(mid+1,r);
    return pos;
}

建树。

st是线段树的简称 ls指向左儿子,rs指向右儿子。ts类似于时间戳。时间戳我们在前向星或者tarjan里面都有见到,是一种常用的小技巧。l,r就是线段树的范围啦。

int update(int ed,int l,int r,int idx,int v){
    if(r<idx || l>idx)return ed;//指向前驱 这就是复杂度没爆炸的原因
    
    ts++;   //空间上每次增加一条树链的大小 空间每次增加为logn
    int pos=ts;
    if(r-l==0){
        st[pos].v=v;
        return pos;
    } 
   int mid=l+(r-l)/2;
   st[pos].ls=update(st[ed].ls,l,mid,idx,v);
   st[pos].rs=update(st[ed].rs,mid+1,r,idx,v);
   return pos;
}

更新节点。其中ed为版本,版本就是那个时间戳的东东,一开始的ed即为头节点的时间戳。idx就是我们想改变的值的下标。v就是值。

void query(int ed,int l,int r,int idx){
    if(r<idx || l>idx)return ;
    if(r-l==0){
        queryans=st[ed].v;
        return ;
    }
    int mid=l+(r-l)/2;
    query(st[ed].ls,l,mid,idx);
    query(st[ed].rs,mid+1,r,idx);
}

查询。

例题:

洛谷3919

#include <bits/stdc++.h>
using namespace std;
const int MAXN=3e7+10;
const int haf=1e6+10;
struct node{
    int v,ls,rs;
};
node st[MAXN];
int ts;
int arr[haf];
int queryans;
int edition[haf];
inline int read(){
    int f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return f*x;
}
int  build(int l,int r){
    ts++;
    int pos=ts;
    if(r-l==0){
        st[pos].v=arr[l];
        return pos;
    }
    int mid=l+(r-l)/2;
    st[pos].ls=build(l,mid);
    st[pos].rs=build(mid+1,r);
    return pos;
}
int update(int ed,int l,int r,int idx,int v){
    if(r<idx || l>idx)return ed;//指向前驱 这就是复杂度没爆炸的原因
    
    ts++;   //空间上每次增加一条树链的大小 空间每次增加为logn
    int pos=ts;
    if(r-l==0){
        st[pos].v=v;
        return pos;
    } 
   int mid=l+(r-l)/2;
   st[pos].ls=update(st[ed].ls,l,mid,idx,v);
   st[pos].rs=update(st[ed].rs,mid+1,r,idx,v);
   return pos;
}
void query(int ed,int l,int r,int idx){
    if(r<idx || l>idx)return ;
    if(r-l==0){
        queryans=st[ed].v;
        return ;
    }
    int mid=l+(r-l)/2;
    query(st[ed].ls,l,mid,idx);
    query(st[ed].rs,mid+1,r,idx);
}

int main(){
    int n,m;n=read();m=read();
    for(int i=0;i<n;i++)arr[i]=read();
    build(0,n-1);
    edition[0]=1;
    for(int i=1;i<=m;i++){
        int ed,op,loc;ed=read();op=read();loc=read();
        int val;
        loc--;
        if(op==1){
            val=read();
            edition[i]=update(edition[ed],0,n-1,loc,val);//本质上每次都拿了个根节点
        }else{
            edition[i]=edition[ed];
            query(edition[ed],0,n-1,loc);
            printf("%d\n",queryans);
        }
        
    }
	return 0;
}

可持久化并查集:

洛谷3402

#include <bits/stdc++.h>
using namespace std;
const int MAXN=4e6+10;
inline int read(){
    int f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return f*x;
}
struct node{
    int l,r;
};
node st[MAXN];
int fa[MAXN];
int ts;
int depth[MAXN];
int edition[MAXN];
int actdepth;
int n,m;
int build(int l,int r){
    int pos=++ts;
    if(r-l==0){
        fa[pos]=l;
        return pos;
    }
    int mid=l+(r-l)/2;
    st[pos].l=build(l,mid);
    st[pos].r=build(mid+1,r);
    return pos;
}
int xpos;
int mer;
void query(int ed,int l,int r,int x){
    if(r<x || l>x)return ;
    if(r-l==0){
        xpos= ed;
        return ;
    }
    int mid=l+(r-l)/2;
    query(st[ed].l,l,mid,x);
    query(st[ed].r,mid+1,r,x);
}
int dis_find(int ed,int x){
    query(ed,0,n-1,x);
    if(fa[xpos]==x)return xpos;
    actdepth++;
    return dis_find(ed,fa[xpos]);
}
int update(int ed,int l,int r,int x,int nxfa){
    if(r<x || l>x)return ed;
    
    int pos=++ts;
    if(r-l==0){
        fa[pos]=nxfa;
        depth[pos]=depth[ed]+(mer?1:0);
        return pos;
    }
    int mid=l+(r-l)/2;
    st[pos].l=update(st[ed].l,l,mid,x,nxfa);
    st[pos].r=update(st[ed].r,mid+1,r,x,nxfa);
    return pos;
}
void add(int ed,int l,int r,int x){
    if(l>x || r<x)return ;
    if(r-l==0){
        depth[ed]++;
        return ;
    }
    int m=l+(r-l)/2;
    add(st[ed].l,l,m,x);
    add(st[ed].r,m+1,r,x);
}
int main(){
    /* initialize random seed: */
    srand (time(NULL));
    edition[0]=1;
    n=read();m=read();
    build(0,n-1);
    for(int i=1;i<=m;i++){
        int op;op=read();
        if(op==1){
            int a,b;a=read();b=read();
            a--;b--;
            actdepth=0;
            int firstf=dis_find(edition[i-1],a);
            int fd=actdepth;
            actdepth=0;
            int secondf=dis_find(edition[i-1],b);
            int sd=actdepth;
            if(fa[firstf]==fa[secondf]){
                edition[i]=edition[i-1];
                continue;
            }
            mer=0;
            if(depth[firstf]<depth[secondf])
                edition[i]=update(edition[i-1],0,n-1,fa[firstf],fa[secondf]);
            else {
                
                edition[i]=update(edition[i-1],0,n-1,fa[secondf],fa[firstf]);
                if(depth[firstf]==depth[secondf]) add(edition[i],0,n-1,fa[firstf]);
            }
           
        }else if(op==2){
            int k;k=read();
            edition[i]=edition[k];
            
        }else if(op==3){
            edition[i]=edition[i-1];
            int a,b;a=read();b=read();
             a--;b--;
            actdepth=0;
            int firstf=dis_find(edition[i-1],a);
            actdepth=0;
            int secondf=dis_find(edition[i-1],b);
            if(fa[firstf]==fa[secondf])puts("1");
            else puts("0");
        }
    }
	return 0;
}

完结。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值