====== 可持久化平衡树 ======
===== 算法简介 =====
一种可以维护历史版本的平衡树,时间复杂度和空间复杂度均为 $O\left(m\log n\right)$。
===== 算法思想 =====
使用类似可持久化线段树的方法继承历史版本,但大部分平衡树都自带旋转操作,节点的父子关系会发生,不利于可持久化。
于是考虑使用 $\text{fhq treap}$ 进行可持久化。
$\text{fhq treap}$ 的核心操作为 $\text{split}$ 和 $\text{merge}$,所以考虑 $\text{split}$ 和 $\text{merge}$ 时动态开点即可。
关于 $\text{merge}$ 操作是否需要动态开点存在争议。事实上,$\text{fhq treap}$ 的 $\text{split}$ 操作会提前为
$\text{merge}$ 操作开点,但考虑下面代码
void erase(int &root,int p,int v){
int lef,mid,rig;
split(p,lef,rig,v);
split(lef,lef,mid,v-1);
merge(mid,node[mid].ch[0],node[mid].ch[1]);
merge(lef,lef,mid);
merge(root,lef,rig);
}
我们会发现 ''merge(mid,node[mid].ch[0],node[mid].ch[1])'' 语句并没有 $\text{split}$ 操作为其开点,这将导致错误。
解决方案为所有相同关键字的节点共用一个位置,或者为 $\text{merge}$ 操作开点。考虑到前者编程较为复杂,这里选择后者。
需要注意的是,在保证关键字唯一或维护序列时 $\text{merge}$ 操作不需要开点。
===== 算法模板 =====
==== 集合维护版本 ====
[[https://www.luogu.com.cn/problem/P3835|洛谷p3835]]
const int MAXN=5e5+5,MAXM=40,Inf=0x7fffffff;
int root[MAXN];
struct fhq_treap{
int tot;
struct Node{
int val,r,sz,ch[2];
}node[MAXN*MAXM];
int Copy(int k){
node[++tot]=node[k];
return tot;
}
int New(int v){
int x=++tot;
node[x].val=v,node[x].r=rand(),node[x].sz=1,node[x].ch[0]=node[x].ch[1]=0;
return x;
}
void pushup(int k){node[k].sz=node[node[k].ch[0]].sz+node[node[k].ch[1]].sz+1;}
void split(int k,int &k1,int &k2,int v){
if(!k) return k1=k2=0,void();
if(node[k].val<=v){
k1=Copy(k);split(node[k].ch[1],node[k1].ch[1],k2,v);pushup(k1);
}else{
k2=Copy(k);split(node[k].ch[0],k1,node[k2].ch[0],v);pushup(k2);
}
}
void merge(int &k,int k1,int k2){
if(!k1||!k2)return k=k1|k2,void();
if(node[k1].r>node[k2].r){
k=Copy(k1);merge(node[k].ch[1],node[k1].ch[1],k2);pushup(k);
}else{
k=Copy(k2);merge(node[k].ch[0],k1,node[k2].ch[0]);pushup(k);
}
}
void insert(int &root,int p,int v){
int lef,rig;
split(p,lef,rig,v);
merge(lef,lef,New(v));
merge(root,lef,rig);
}
void erase(int &root,int p,int v){
int lef,mid,rig;
split(p,lef,rig,v);
split(lef,lef,mid,v-1);
merge(mid,node[mid].ch[0],node[mid].ch[1]);
merge(lef,lef,mid);
merge(root,lef,rig);
}
int rank(int root,int v){
int pos=root,ans=0;
while(true){
if(!pos)return ans+1;
if(node[pos].val=v)pos=node[pos].ch[0];
else ans=max(ans,node[pos].val),pos=node[pos].ch[1];
}
return ans;
}
int suf(int root,int v){
int pos=root,ans=Inf;
while(pos){
if(node[pos].val<=v)pos=node[pos].ch[1];
else ans=min(ans,node[pos].val),pos=node[pos].ch[0];
}
return ans;
}
}tree;
int main()
{
int n=read_int(),v,opt,x;
_rep(i,1,n){
v=read_int(),opt=read_int(),x=read_int();
switch(opt){
case 1:
tree.insert(root[i],root[v],x);
break;
case 2:
tree.erase(root[i],root[v],x);
break;
case 3:
enter(tree.rank(root[i]=root[v],x));
break;
case 4:
enter(tree.kth(root[i]=root[v],x));
break;
case 5:
enter(tree.pre(root[i]=root[v],x));
break;
case 6:
enter(tree.suf(root[i]=root[v],x));
break;
}
}
return 0;
}
==== 序列维护版本 ====
[[https://www.luogu.com.cn/problem/P5055|洛谷p5055]]
需要注意的是,可持续化数据结构的标记下放操作需要复制子节点再下方。
原因是可持续化数据结构多个父节点共用一个子节点,若直接对标记进行下放,将影响多个父节点,即影响多个历史版本。
const int MAXN=2e5+5,MAXM=80;
int root[MAXN];
struct fhq_treap{
int tot;
struct Node{
int r,val,sz,flip,ch[2];
LL sum;
}node[MAXN*MAXM];
int New(int v){
int x=++tot;
node[x].sum=node[x].val=v,node[x].r=rand(),node[x].sz=1;
return x;
}
int Copy(int k){
node[++tot]=node[k];
return tot;
}
void pushup(int k){
node[k].sz=node[node[k].ch[0]].sz+node[node[k].ch[1]].sz+1;
node[k].sum=node[node[k].ch[0]].sum+node[node[k].ch[1]].sum+node[k].val;
}
void pushdown(int k){
if(node[k].flip){
if(node[k].ch[0])node[k].ch[0]=Copy(node[k].ch[0]);
if(node[k].ch[1])node[k].ch[1]=Copy(node[k].ch[1]);
swap(node[k].ch[0],node[k].ch[1]);
node[node[k].ch[0]].flip^=1;
node[node[k].ch[1]].flip^=1;
node[k].flip=0;
}
}
void split(int k,int &k1,int &k2,int rk){
if(!k) return k1=k2=0,void();
pushdown(k);
if(node[node[k].ch[0]].sz+1<=rk){
k1=Copy(k);split(node[k].ch[1],node[k1].ch[1],k2,rk-node[node[k].ch[0]].sz-1);pushup(k1);
}else{
k2=Copy(k);split(node[k].ch[0],k1,node[k2].ch[0],rk);pushup(k2);
}
}
void merge(int &k,int k1,int k2){
if(!k1||!k2)return k=k1|k2,void();
if(node[k1].r>node[k2].r){
pushdown(k1);k=k1;merge(node[k].ch[1],node[k1].ch[1],k2);pushup(k);
}else{
pushdown(k2);k=k2;merge(node[k].ch[0],k1,node[k2].ch[0]);pushup(k);
}
}
void Flip(int &root,int p,int L,int R){
int lef,mid,rig;
split(p,lef,rig,R);
split(lef,lef,mid,L-1);
node[mid].flip^=1;
merge(lef,lef,mid);
merge(root,lef,rig);
}
void Insert(int &root,int p,int pos,int v){
int lef,rig;
split(p,lef,rig,pos);
merge(lef,lef,New(v));
merge(root,lef,rig);
}
void erase(int &root,int p,int pos){
int lef,mid,rig;
split(p,lef,rig,pos);
split(lef,lef,mid,pos-1);
merge(root,lef,rig);
}
LL query(int root,int L,int R){
int lef,mid,rig;
split(root,lef,rig,R);
split(lef,lef,mid,L-1);
return node[mid].sum;
}
}tree;
int main()
{
int n,v,opt;
LL p,x,l,r,last=0;
n=read_int();
_rep(i,1,n){
v=read_int(),opt=read_int();
switch(opt){
case 1:
p=read_LL()^last,x=read_LL()^last;
tree.Insert(root[i],root[v],p,x);
break;
case 2:
p=read_LL()^last;
tree.erase(root[i],root[v],p);
break;
case 3:
l=read_LL()^last,r=read_LL()^last;
tree.Flip(root[i],root[v],l,r);
break;
case 4:
l=read_LL()^last,r=read_LL()^last;
enter(last=tree.query(root[i]=root[v],l,r));
break;
}
}
return 0;
}