可持久化并查集

前置知识

可持久化线段树

本质上只是可持久化数组

按秩合并

因为正常写并查集的路径压缩时间复杂度是均摊保证的,所以可以构造数据使可持久化后复杂度爆炸。
所以将并查集按秩合并,保证树长始终为 \(\log n\) 级别的。
只需要记录一下每个点的子树的深度,将小的合并到大的上。

void merge(int root,int x,int y)
{
	int f1=find(x,rt[root]),f2=find(y,rt[root]);//在可持久化线段树中找出x,y的位置
	if(sh[f1].fa!=sh[f2].fa )
	{
		if(sh[f1].dep<sh[f2].dep)swap(f1,f2);
		rt[root]=modify1(rt[root],1,n,sh[f2].fa,sh[f1].fa);//将fa[f2]改为f1 
		if(sh[f1].dep==sh[f2].dep)rt[root]=modify2(rt[root],1,n,sh[f1].fa);//将dep[f1]加一 	
	}	
}

思路

对于 \(fa,dep\) 进行可持久化。
1.合并操作,就是按秩合并,将其转化为两个修改操作,修改某一个点的父亲和某一个点的dep。

2.修改 \(rt\) 数组即可。

3.直接暴力向上找到并查集的根,比较即可。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct node
{
	int fa,dep,ls,rs;
}sh[N*60];
int rt[N*2],cnt,n,m;
int news(int x)
{
	int nx=++cnt;
	sh[nx]=sh[x];
	return nx;
}
int query(int x,int l,int r,int wz)
{
	if(l==r){return x;} 
	int mid=(l+r)>>1;
	if(wz<=mid)return query(sh[x].ls,l,mid,wz);
	else return query(sh[x].rs,mid+1,r,wz);
}
int find(int x,int root)
{
	int f=query(root,1,n,x);int f1=sh[query(root,1,n,x)].fa;
	if(x==f1)return f;
	else return find(f1,root); 
}
int modify1(int x,int l,int r,int wz,int z)
{
	x=news(x);//新建 
	if(l==r){sh[x].fa=z;return x;} 
	int mid=(l+r)>>1;
	if(wz<=mid)sh[x].ls=modify1(sh[x].ls,l,mid,wz,z);
	else sh[x].rs=modify1(sh[x].rs,mid+1,r,wz,z);
	return x;
} 
int modify2(int x,int l,int r,int wz)
{
	x=news(x);//新建 
	if(l==r){sh[x].dep++;return x; } 
	int mid=(l+r)>>1;
	if(wz<=mid)sh[x].ls=modify2(sh[x].ls,l,mid,wz);
	else sh[x].rs=modify2(sh[x].rs,mid+1,r,wz);
	return x;
} 
void merge(int root,int x,int y)
{
	int f1=find(x,rt[root]),f2=find(y,rt[root]);
	if(sh[f1].fa!=sh[f2].fa )
	{
		if(sh[f1].dep<sh[f2].dep)swap(f1,f2);
		rt[root]=modify1(rt[root],1,n,sh[f2].fa,sh[f1].fa);//将fa[f2]改为f1 
		if(sh[f1].dep==sh[f2].dep)rt[root]=modify2(rt[root],1,n,sh[f1].fa);//将dep[f1]加一 	
	}	
}
bool pan(int root,int x,int y)
{
	int f1=find(x,rt[root]),f2=find(y,rt[root]);
	if(sh[f2].fa==sh[f1].fa)return 1;
	else return 0;
} 
int build(int x,int l,int r)
{
	x=news(x);
	if(l==r){sh[x].fa=l;return x;} 
	int mid=(l+r)>>1;
	sh[x].ls=build(sh[x].ls,l,mid);sh[x].rs=build(sh[x].rs,mid+1,r);
	return x;
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	rt[0]=build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int opt,a,b;cin>>opt;
		rt[i]=rt[i-1]; //注意 
		if(opt==1)
		{
			cin>>a>>b;
			merge(i,a,b);
		} 
		if(opt==2)
		{
			cin>>a;
			rt[i]=rt[a];
		}
		if(opt==3)
		{
			cin>>a>>b;
			if(pan(i,a,b))cout<<"1"<<'\n';
			else cout<<"0"<<'\n';
		}
	}
	return 0;
} 

常见错误

1.将主席树上的点与序列上的点混用
2.没有初始化fa数组

例题

模板题

就是上面的思路和代码。

posted @ 2025-03-11 15:44  exCat  阅读(96)  评论(0)    收藏  举报