一些树形数据结构模板(可持久化)

可持久化相关


可持久化数组

例题:维护一个数组,支持保存历史版本,支持在历史版本上修改、查询。洛谷 P3919

void insert(int &x,int u,int l,int r,int p,int k){
	x=++tot;
	tr[x]=tr[u],ls[x]=ls[u],rs[x]=rs[u];
	if(l==r){
		tr[x]=k;
		return;
	}
	int mid=l+r>>1;
	if(p<=mid)
		insert(ls[x],ls[u],l,mid,p,k);
	else insert(rs[x],rs[u],mid+1,r,p,k);
}

int query(int x,int l,int r,int p){
	if(l==r)
		return tr[x];
	int mid=l+r>>1;
	if(p<=mid)
		return query(ls[x],l,mid,p);
	return query(rs[x],mid+1,r,p);	
}

非常简单且好写,注意每次 insert 都要 x=++tot;,以保证不会修改到历史版本。

可持久化权值线段树(主席树)

例题:静态区间第 \(k\) 小。洛谷 P3834

考虑全局第 \(k\) 小如何做。
显然,可以把所有数插进权值线段树,查询时在权值线段树上二分即可。
区间第 \(k\) 小也可以用类似的做法,考虑区间 \([l,r]\) 可以差分成 \([1,l-1],[1,r]\),查询时在两颗树上二分即可。
\(n\) 颗权值线段树显然会爆空间,可持久化即可。

void insert(int &x,int u,int l,int r,int k){
	x=++cnt;
	tr[x]=tr[u]+1,ls[x]=ls[u],rs[x]=rs[u];
	if(l==r)
		return;
	int mid=l+r>>1;
	if(k<=mid)
		insert(ls[x],ls[u],l,mid,k);
	else insert(rs[x],rs[u],mid+1,r,k);	
}

int query(int x,int u,int l,int r,int k){
	if(l==r)
		return l;
	int mid=l+r>>1;
	if(tr[ls[u]]-tr[ls[x]]>=k)
		return query(ls[x],ls[u],l,mid,k);
	return query(rs[x],rs[u],mid+1,r,k-(tr[ls[u]]-tr[ls[x]]));
}

似乎没有什么实现上的难点。

可持久化并查集

例题:维护若干个集合,支持回退到历史版本,支持合并与查询。洛谷 P3402
发现合并两个集合 \(x,y\) 时,我们对于 \(fa\) 数组进行修改,因此本质上是维护可持久化数组。
注意不能路径压缩,考虑一个版本路径压缩后,在后续版本中不一定会被复用,多次修改 \(fa\) 数组导致时间开销过大。
因此考虑按秩合并,再维护一个 \(dep\) 数组即可。(具体实现可以封装可持久化数组)

struct tree{
	int tr[N<<7],ls[N<<7],rs[N<<7],root[N],cnt;
	void insert(int &x,int u,int l,int r,int p,int c){
		x=++cnt;
		tr[x]=tr[u],ls[x]=ls[u],rs[x]=rs[u];
		if(l==r){
			tr[x]=c;
			return;
		}
		int mid=l+r>>1;
		if(p<=mid)
			insert(ls[x],ls[u],l,mid,p,c);
		else insert(rs[x],rs[u],mid+1,r,p,c);
	}
	int query(int x,int l,int r,int p){
		if(l==r)
			return tr[x];
		int mid=l+r>>1;
		if(p<=mid)
			return query(ls[x],l,mid,p);
		return query(rs[x],mid+1,r,p);
	}
	int find(int x,int v){
		int fa=query(v,1,n,x);
		if(fa==x)
			return x;
		return find(fa,v);
	}		
}

可持久化平衡树

例题:维护一个数据结构,支持基于历史版本插入、删除、查询排名、查询第 \(k\) 小、求前驱后继。洛谷 P3835
考虑 FHQ-Treap 的可持久化,仅需在 split 和 merge 时新建节点即可,代码也不算难写。

int newnode(int k){
	pri[++idx]=rnd();
	siz[idx]=1,val[idx]=k;
	return idx;
}
int clone(int x){
	pri[++idx]=rnd();
	siz[idx]=siz[x],val[idx]=val[x];
	ls[idx]=ls[x],rs[idx]=rs[x];
	return idx;
}
void pushup(int x){
	siz[x]=siz[ls[x]]+siz[rs[x]]+1;
}
void split(int u,int k,int &x,int &y){
	if(!u){
		x=y=0;
		return;
	}
	int p=clone(u);
	if(k<val[p]){
		y=p;
		split(ls[u],k,x,ls[y]);
	}else{
		x=p;
		split(rs[u],k,rs[x],y);
	}
	pushup(p);
}
int merge(int x,int y){
	if(!x||!y)
		return x+y;
	int p;
	if(pri[x]<pri[y]){
		p=clone(x);
		rs[p]=merge(rs[p],y);
	}else{
		p=clone(y);
		ls[p]=merge(x,ls[p]);
	}
	pushup(p);
	return p;
}
void insert(int &rt,int ort,int k){
	int x,y,z;
	split(ort,k,x,y);
	z=newnode(k);
	rt=merge(merge(x,z),y);
}
void del(int &rt,int ort,int k){
	int x,y,z;
	split(ort,k,x,y);
	split(x,k-1,x,z);
	if(z)
		z=merge(ls[z],rs[z]);
	rt=merge(merge(x,z),y);
}
int rk(int u,int k){
	if(!u)
		return 1;
	if(k<=val[u])
		return rk(ls[u],k);
	return siz[ls[u]]+1+rk(rs[u],k);
}
int kth(int u,int k){
	if(k<=siz[ls[u]])
		return kth(ls[u],k);
	if(k<=siz[ls[u]]+1)
		return val[u];
	return kth(rs[u],k-siz[ls[u]]-1);
}
posted @ 2026-08-16 00:10  _M28  阅读(15)  评论(0)    收藏  举报