可持久化并查集
前置知识
可持久化线段树
本质上只是可持久化数组
按秩合并
因为正常写并查集的路径压缩时间复杂度是均摊保证的,所以可以构造数据使可持久化后复杂度爆炸。
所以将并查集按秩合并,保证树长始终为 \(\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数组
例题
模板题
就是上面的思路和代码。

浙公网安备 33010602011771号