可持久化伴性遗传(更新中)

1.可持久化线段树
板1

与普通线段树不同地方,在于它是基于线段树的某一个历史版本进行修改、查询。 每次进行操作时,我们都新建一个节点表示新版本的根节点。

注意:节点的左右儿子编号不再是节点编号\(*2\)\(*2+1\),所以我们要开一个结构体存每个节点的左右儿子。注意空间开大点。

点击查看
#include<bits/stdc++.h>
#define l(i) tr[i].l
#define r(i) tr[i].r
#define pr(i) tr[i].pre
#define mid ((l+r)>>1)
using namespace std;
const int N=1e6+10;
int a[N*10],rt[N*10],cnt,n,m;
struct tree{
	int l,r,pre;
}tr[N*30];
inline int read()
{
	int s=0,x=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')x=-1;ch=getchar();}
	while(isdigit(ch))s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return s*x;
}
inline int copy(int old)
{
	tr[++cnt]=tr[old];
	return cnt;
}
inline int build(int i,int l,int r)
{
	i=++cnt;
	if(l==r)
	{
		pr(i)=a[l];
		return i;
	}
	l(i)=build(l(i),l,mid);
	r(i)=build(r(i),mid+1,r);
	return i;
}
inline int update(int i,int l,int r,int x,int k)
{
	i=copy(i);
	if(l==r)
	{
		pr(i)=k;
		return i;
	}
	if(x<=mid)l(i)=update(l(i),l,mid,x,k);
	else r(i)=update(r(i),mid+1,r,x,k);
	return i;
}
int getsum(int i,int l,int r,int x)
{
	if(l==r)return pr(i);
	if(x<=mid)return getsum(l(i),l,mid,x);
	else return getsum(r(i),mid+1,r,x);
}
int main()
{
	n=read(),m=read();
	for(register int i=1;i<=n;++i)a[i]=read();
	rt[0]=build(0,1,n);
	for(register int i=1;i<=m;++i)
	{
		int v=read(),opt=read(),loc=read();
		if(opt==1)
		{
			int val=read();
			rt[i]=update(rt[v],1,n,loc,val);
		}
		else
		{
			printf("%d\n",getsum(rt[v],1,n,loc));
			rt[i]=rt[v];
		}
	}
	return 0;
}

板2

主席树。

我们在原序列中的每个位置都开一棵线段树,第\(i\)棵线段树上的区间\([x,y]\)表示\(1\)~\(i\)中在\([x,y]\)中数的个数。由于原题的值域范围很大,所以我们需要进行离散化。其他地方就和板1一样了。

点击查看
#include<bits/stdc++.h>
#define siz(i) tr[i].siz
#define l(i) tr[i].l
#define r(i) tr[i].r
#define mid (l+r>>1)
using namespace std;
const int N=2e5+10;
inline int read()
{
	int s=0;char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return s;
}
int n,q,a[N],b[N],rt[N],m;
int cnt;
struct node{
	int siz,l,r;
}tr[N<<5];
inline int copy(int old)
{
	tr[++cnt]=tr[old];
	return cnt;
}
inline int build(int i,int l,int r)
{
	i=++cnt;
	if(l==r)return i;
	l(i)=build(l(i),l,mid);
	r(i)=build(r(i),mid+1,r);
	siz(i)=siz(l(i))+siz(r(i));
	return i;
}
inline int update(int i,int l,int r,int x)
{
	i=copy(i);
	if(l==r)
	{
		siz(i)++;
		return i;
	}
	if(x<=mid)l(i)=update(l(i),l,mid,x);
	else r(i)=update(r(i),mid+1,r,x);
	siz(i)=siz(l(i))+siz(r(i));
	return i;
}
int getsum(int x,int y,int l,int r,int k)
{
	if(l==r)return l;
	int siz=siz(l(y))-siz(l(x));
	if(siz>=k)return getsum(l(x),l(y),l,mid,k);
	else return getsum(r(x),r(y),mid+1,r,k-siz);
}
int main()
{
	n=read();q=read();
	for(register int i=1;i<=n;++i)b[i]=a[i]=read();
	sort(b+1,b+n+1);
	m=unique(b+1,b+n+1)-b-1;
	for(register int i=1;i<=n;++i)a[i]=lower_bound(b+1,b+m+1,a[i])-b;
	rt[0]=build(0,1,n);
	for(register int i=1;i<=n;++i)rt[i]=update(rt[i-1],1,n,a[i]);
	while(q--)
	{
		int l=read(),r=read(),k=read();
		printf("%d\n",b[getsum(rt[l-1],rt[r],1,n,k)]);
	}
	return 0;
}

2.可持久化平衡树
先来看fhq。我们观察它的几个基本操作,发现会对可持久化造成影响的只有merge(合并)和split(分裂)两个操作。
我们用root[]数组存下每个版本的根节点,对于它的可持久化操作,我们惊人地发现:它和主席树的思想没什么区别。
记得空间开大点。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e5+10;
int n,son[N*50][2],siz[N*50],bal[N*50];
int cnt,root[N];
ll val[N*50];
void copy(int x,int y)
{
	son[x][1]=son[y][1];son[x][0]=son[y][0];
	siz[x]=siz[y];bal[x]=bal[y];val[x]=val[y];
}
void update(int x){siz[x]=(son[x][0]?siz[son[x][0]]:0)+(son[x][1]?siz[son[x][1]]:0)+1;}
int build(ll w=0)
{
	siz[++cnt]=1;
	val[cnt]=w;bal[cnt]=rand();
	return cnt;
}
int merge(int x,int y)
{
	if(!x||!y)return x|y;
	int rt=build();
	if(bal[x]<bal[y])
	{
		copy(rt,x);
		son[rt][1]=merge(son[rt][1],y);
		update(rt);
		return rt;
	}
	copy(rt,y);
	son[rt][0]=merge(x,son[rt][0]);
	update(rt);
	return rt;
}
void split(int rt,ll k,int &x,int &y)
{
	if(!rt)x=y=0;
	else
	{
		if(val[rt]<=k)
		{
			x=build();
			copy(x,rt);
			split(son[x][1],k,son[x][1],y);
			update(x);
		}
		else
		{
			y=build();
			copy(y,rt);
			split(son[y][0],k,x,son[y][0]);
			update(y);
		}
	}
}
int find(int rt,ll k)
{
	while(1)
	{
		if(siz[son[rt][0]]>=k)rt=son[rt][0];
		else
		{
			if(son[rt][0])k-=siz[son[rt][0]];
			if(!--k)return rt;
			rt=son[rt][1];
		}
	}
}
int tr1,tr2,tr3;
int main()
{
	srand(time(0));
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		tr1=tr2=tr3=0;
		int v,opt;ll x;scanf("%d %d %lld",&v,&opt,&x);
		root[i]=root[v];
		if(opt==1)
		{
			split(root[i],x,tr1,tr2);
			root[i]=merge(merge(tr1,build(x)),tr2);
		}
		else if(opt==2)
		{
			split(root[i],x,tr1,tr3);
			split(tr1,x-1,tr1,tr2);
			tr2=merge(son[tr2][0],son[tr2][1]);
			root[i]=merge(merge(tr1,tr2),tr3);
		}
		else if(opt==3)
		{
			split(root[i],x-1,tr1,tr2);
			printf("%d\n",siz[tr1]+1);
			root[i]=merge(tr1,tr2);
		}
		else if(opt==4)printf("%lld\n",val[find(root[i],x)]);
		else if(opt==5)
		{
			split(root[i],x-1,tr1,tr2);
			if(tr1==0)
			{
				puts("-2147483647");
				continue;
			}
			printf("%lld\n",val[find(tr1,siz[tr1])]);
			root[i]=merge(tr1,tr2);
		}
		else if(opt==6)
		{
			split(root[i],x,tr1,tr2);
			if(tr2==0)
			{
				puts("2147483647");
				continue;
			}
			printf("%lld\n",val[find(tr2,1)]);
			root[i]=merge(tr1,tr2);
		}
	}
	return 0;
}

对于splay,我们发现它太麻烦了,所以直接跳过

3.可持久化文艺平衡树
首先,还是用fhq。常数比splay小,实现比splay简单,思路比splay好像,傻子才用splay
操作:
1.插入:把平衡树在位置p处分裂,然后把x和分裂出来的两棵平衡树合并。
2.删除:把平衡树分裂为[1,p-1],p,[p+1,n]三课树,然后将左右两棵树合并。
3.翻转:把区间[l,r]拆下来,然后打上标记再安回去。
4.求和:提前用一个数组sum存平衡树上的区间和,求和时,把区间[l,r]拆下来,求和,输出。
注意:分裂/合并时要先推懒标记再分裂/合并。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define l(p) tr[p].l
#define r(p) tr[p].r
#define val(p) tr[p].val
#define lazy(p) tr[p].lazy
#define rnd(p) tr[p].rnd
#define siz(p) tr[p].siz
#define sum(p) tr[p].sum
using namespace std;
const int N=2e5+10;
struct fhq{
	ll val,sum;
	int l,r,lazy,rnd,siz;
}tr[N<<7];
int rt[N],cnt;
ll lastans;
inline int newnode(ll w=0)
{
	val(++cnt)=w;sum(cnt)=w;
	rnd(cnt)=rand();siz(cnt)=1;
	return cnt;
}
inline int copy(int old)
{
	int nw=newnode();
	tr[nw]=tr[old];
	return nw;
}
void pushdown(int p)
{
	if(!lazy(p))return ;
	if(l(p))l(p)=copy(l(p));
	if(r(p))r(p)=copy(r(p));
	swap(l(p),r(p));
	if(l(p))lazy(l(p))^=1;
	if(r(p))lazy(r(p))^=1;
	lazy(p)=0;
}
void pushup(int p)
{
	siz(p)=siz(l(p))+siz(r(p))+1;
	sum(p)=sum(l(p))+sum(r(p))+val(p);
}
inline void split(int p,int k,int &x,int &y)
{
	if(!p)
	{
		x=y=0;
		return ;
	}
	pushdown(p);
	if(siz(l(p))<k)
	{
		x=copy(p);
		split(r(x),k-siz(l(p))-1,r(x),y);
		pushup(x);
	}
	else
	{
		y=copy(p);
		split(l(y),k,x,l(y));
		pushup(y);
	}
}
int merge(int x,int y)
{
	if(!x||!y)return x|y;
	pushdown(x);pushdown(y);
	if(rnd(x)<rnd(y))
	{
		r(x)=merge(r(x),y);
		pushup(x);
		return x;
	}
	l(y)=merge(x,l(y));
	pushup(y);
	return y;
}
inline int read()
{
	int s=0,x=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')x=-1;ch=getchar();}
	while(isdigit(ch))s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return s*x;
}
int x,y,z;
signed main()
{
	int n=read();
	for(int i=1;i<=n;++i)
	{
		x=y=z=0;
		int v=read(),opt=read();
		switch(opt)
		{
			case 1:{
				int p=read()^lastans,a=read()^lastans;
				split(rt[v],p,x,y);
				rt[i]=merge(merge(x,newnode(a)),y);
				break;
			}
			case 2:{
				int p=read()^lastans;
				split(rt[v],p,x,y);
				split(x,p-1,x,z);
				rt[i]=merge(x,y);
				break;
			}
			case 3:{
				int a=read()^lastans,b=read()^lastans;
				split(rt[v],b,x,y);
				split(x,a-1,x,z);
				lazy(z)^=1;
				rt[i]=merge(merge(x,z),y);
				break;
			}
			case 4:{
				int a=read()^lastans,b=read()^lastans;
				split(rt[v],b,x,y);
				split(x,a-1,x,z);
				printf("%lld\n",lastans=sum(z));
				rt[i]=merge(merge(x,z),y);
				break;
			}
		}
	}
	return 0;
}

3.可持久化trie

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=6e5+10;
int idx,tr[N<<5][3],root[N],last[N<<5];
int l,r,val,a,b,c,sum[N],n,m;
inline void insert(int pre,int now,int i,int k)
//上一个版本根位置,当前版本根位置,插入序号,插入到哪一位 
{
	if(k<0)
	{
		last[now]=i;//更新最晚插入版本 
		return;
	}
	bool id=(sum[i]>>k)&1;//寻找插入字符 
	if(pre)tr[now][id^1]=tr[pre][id^1];//更新当前版本 
	tr[now][id]=++idx;//新建节点 
	insert(tr[pre][id],tr[now][id],i,k-1);
	last[now]=max(last[tr[now][0]],last[tr[now][1]]);//更新最晚插入版本 
}
int find(int l,int now,int val)
//左边界,右边界对应根的位置,查询值 
{
    int p=now;
    for(int i=23;i>=0;--i)
    {
    	bool id=(val>>i)&1;
    	if(last[tr[p][id^1]]>=l)p=tr[p][id^1];
    	else p=tr[p][id];
	}
	return sum[last[p]]^val;
}
inline int read()
{
	int s=0;char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return s;
}
int main()
{
	n=read(),m=read();
	root[0]=++idx;
	last[0]=-1;
	insert(0,root[0],0,23);
	for(int i=1;i<=n;++i)
	{
		a=read();
		sum[i]=sum[i-1]^a;
		root[i]=++idx;
		insert(root[i-1],root[i],i,23);
	}
	while(m--)
	{
		char op;cin>>op;
		if(op=='A')
		{
			a=read();
			n++;
			sum[n]=sum[n-1]^a;
			root[n]=++idx;
			insert(root[n-1],root[n],n,23);
		}
		else
		{
			a=read(),b=read(),c=read();
			printf("%d\n",find(a-1,root[b-1],sum[n]^c));
		}
	}
	return 0;
}

拓:基于主席树实现的可持久化并查集
与普通并查集相比,唯一多出来的就是回退操作。容易看出,两个版本之间根本差别就在于fa数组。
我们尝试对fa数组可持久化。具体的,我们建一棵主席树,树上的叶子代表编号为x的父亲。
之前我们对于并查集之间的合并采用的优化方法是路径压缩,但在可持久化中,我们无法再用路径压缩优化。
原因:
路径压缩的复杂度是均摊的,但是均摊在可持久化中是万万不可接受的,因为可能存在某一次的复杂度是\(n^2\)的,而在我们的操作中有返回之前的版本,万恶的出题人可能会找到一次复杂度\(n^2\)的操作,然后让你不停地回退,重复操作,这样你就会被卡似。
so,我们需要一种单次严格\(logn\)的合并方法,这时候,按秩合并就出现了。
按秩合并有三种方式:
1.深度
2.大小
3.随机
这里只讲第一个第二个不会,第三个被卡掉了
按照深度合并的话,我们需要记录节点的\(fa\)和深度\(dep\)
对于一次操作合并\(x\)\(y\)
我们让\(x=find(x)\)\(y=find(y)\)
(假定\(dep_y\)\(\le\)\(dep_x\))显然是把\(y\)合并到\(x\)的下面,这样才能使我们的深度更小。
考虑合并对深度的影响:

  • \(dep_x\)\(>\)\(dep_y\),则深度不变。
  • \(dep_x\)\(=\)\(dep_y\),则\(dep_x+1\)

这样合并,单次复杂度是严格\(logn\)的,全部合并完,树最高也只有\(logn\)
具体的,对于一次修改,我们要新建两个版本。
把这个版本中\(fa_y\)改成\(x\),然后修改\(dep_x\)

点击查看代码
#include<bits/stdc++.h>
#define fa(x) tr[x].fa
#define l(x) tr[x].l
#define r(x) tr[x].r
#define dep(x) tr[x].dep
#define mid (l+r>>1)
using namespace std;
const int N=1e5+10;
int rt[N<<5],a[N<<5],n,m,cnt;
struct node{
	int fa,l,r,dep;
}tr[N<<5];
int copy(int x){tr[++cnt]=tr[x];return cnt;}
int build(int i,int l,int r)
{
	i=++cnt;
	if(l==r)
	{
		dep(i)=1;fa(i)=l;
		return i;
	}
	l(i)=build(l(i),l,mid);
	r(i)=build(r(i),mid+1,r);
	return i;
}
int findid(int i,int l,int r,int x)
{
	if(l==r)return i;
	if(x<=mid)return findid(l(i),l,mid,x);
	return findid(r(i),mid+1,r,x);
}
int find(int i,int idx,int b)
{
	if(fa(idx)==i)return i;
	return find(fa(idx),findid(b,1,n,fa(idx)),b);
}
int merge(int i,int l,int r,int x,int y)
{
	i=copy(i);
	if(l==r)
	{
		fa(i)=y;
		return i;
	}
	if(x<=mid)l(i)=merge(l(i),l,mid,x,y);
	else r(i)=merge(r(i),mid+1,r,x,y);
	return i;
}
int add(int i,int l,int r,int x)
{
	i=copy(i);
	if(l==r)
	{
		dep(i)++;
		return i;
	}
	if(x<=mid)l(i)=add(l(i),l,mid,x);
	else r(i)=add(r(i),mid+1,r,x);
	return i;
}
void solve(int i,int x,int y)
{
	rt[i]=rt[i-1];
	int Fa=find(x,findid(rt[i],1,n,x),rt[i]),Fb=find(y,findid(rt[i],1,n,y),rt[i]);
	if(Fa==Fb)return ;
	int ida=findid(rt[i],1,n,Fa),idb=findid(rt[i],1,n,Fb);
	if(dep(ida)>dep(idb))swap(Fa,Fb);
	rt[i]=merge(rt[i],1,n,Fa,Fb);
	if(dep(ida)==dep(idb))rt[i]=add(rt[i],1,n,Fb);
}
int main()
{
	scanf("%d %d",&n,&m);
	rt[0]=build(0,1,n);
	for(int i=1;i<=m;++i)
	{
		int opt;scanf("%d",&opt);
		switch(opt)
		{
			case 1:{
				int a,b;scanf("%d %d",&a,&b);
				solve(i,a,b);
				break;
			}
			case 2:{
				int k;scanf("%d",&k);
				rt[i]=rt[k];
				break;
			}
			case 3:{
				int a,b;scanf("%d %d",&a,&b);
				rt[i]=rt[i-1];
				int Fa=find(a,findid(rt[i],1,n,a),rt[i]),Fb=find(b,findid(rt[i],1,n,b),rt[i]);
				Fa==Fb?puts("1"):puts("0");
				break;
			}
		}
	}
	return 0;
}
posted @ 2025-04-02 18:15  leizepromax  阅读(57)  评论(0)    收藏  举报