因为我是菜鸡,如果有说错的话,恳请大佬们指出 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;
}
完结。