学完赶快记下来,免得忘掉……
y1s1,可持久化并查集与普通的并查集基本没什么联系,反而借用了主席树的思路多一点。
回想普通的并查集,为了保证可持久化,如果还使用路径压缩合并,代价是相当大的,为了保证复杂度,启发式合并(按秩合并)是更好的选择。
启发式合并依赖于树的高度(即“秩”),将秩低的合并进秩高的,保证单次复杂度 \(O(\log n)\)。并且这样不会像路径压缩一样完全改变树的形态,方便可持久化。
下面来说一下具体操作。
先来康康找 fa 的操作吧:
int find(int ver,int x) //ver表示当前的版本,x表示要找的
{
int rt=query(ver,1,n,x);
if(x==t[rt].fa) return rt; //这个和普通并查集一样
return find(ver,t[rt].fa);
}
其实和普通并查集操作是一样一样的,只不过由于我们没有路径压缩,并不能一次递归搞定,并且我们把 fa 数组搬到了主席树上,所以得先查询出 x 在主席树上的位置。
然后再来康康合并操作:
if(op==1) //合并
{
root[i]=root[i-1]; //继承之前的版本
scanf("%d%d",&a,&b);
int x=find(root[i],a);
int y=find(root[i],b);
if(t[x].fa==t[y].fa) continue; //在一个集合里,不用合并
if(t[x].dep>t[y].dep) swap(x,y); //始终保证x的秩小,方便操作
change(root[i],root[i-1],1,n,t[x].fa,t[y].fa); //秩小的被合并进秩大的,体现在主席树上就是这个节点的单点修改
if(t[x].dep==t[y].dep) add(root[i],1,n,t[y].fa);
//较为特殊的情况,当合并两个点秩相同时,将最终合并的节点秩+1
}
剩下的东西非常简单,就是基本的主席树的操作,不在赘述。
关于可持久化并查集的一些原则问题,这位大佬说的和我的想法不谋而合,在这orz一下
首先我感觉其他的题解都或多或少出现了一些小问题,这里来发一篇比较正确的题解。
首先可持久化并查集我们是用主席树来维护的,维护 2 个量:每个点的父亲和每棵树的根节点的高度。 那么不使用路径压缩的话这个版本的主席树和上个版本的主席树有且只有一个点会被改动。
那么就可以进行可持久化了。
既然取消了路径压缩,为了保证复杂度,我们需要进行按秩合并。
按秩合并其实就是,我们以树高为秩,每次将树高低的树合并到树高高的树上,那么对于树高高的树来说树高并没有变(也就说原树访问根节点的代价并没有变),而树高低的树访问根节点的代价只增加了 1(这棵树的根节点变成了树高高的树的儿子)。
还有一种特殊情况那就是两棵树树高相同。这个时候把树 A 合并到树 B上 B 的树高会增加 1,而树 A 的高度则不会改变。
所以只需要改动一个点,并不是 @TPLY 所说的改动一群点(他的代码里写的也是只改一个点的代码,自相矛盾)。
也不是 @pengym 所说的不可能有 2 个点深度一样。
最后的完整代码:
#include
#include
using namespace std;
const int N=2e5+5;
struct Tree
{
int l,r,fa,dep;
#define l(x) t[x].l
#define r(x) t[x].r
}t[N*30];
int n,m,root[N],cntnode;
void build(int &rt,int l,int r)
{
rt=++cntnode;
if(l==r) {t[rt].fa=l; return;}
int mid=l+r>>1;
build(l(rt),l,mid);
build(r(rt),mid+1,r);
}
void change(int &rt,int last,int l,int r,int pos,int x)
{
rt=++cntnode;
if(l==r)
{
t[rt].fa=x; t[rt].dep=t[last].dep;
return;
}
int mid=l+r>>1;
l(rt)=l(last),r(rt)=r(last);
if(pos<=mid) change(l(rt),l(last),l,mid,pos,x);
else change(r(rt),r(last),mid+1,r,pos,x);
}
int query(int rt,int l,int r,int pos)
{
if(l==r) return rt;
int mid=l+r>>1;
if(pos<=mid) return query(l(rt),l,mid,pos);
else return query(r(rt),mid+1,r,pos);
}
void add(int rt,int l,int r,int pos)
{
if(l==r) {++t[rt].dep; return;}
int mid=l+r>>1;
if(pos<=mid) return add(l(rt),l,mid,pos);
else return add(r(rt),mid+1,r,pos);
}
int find(int ver,int x)
{
int rt=query(ver,1,n,x);
if(x==t[rt].fa) return rt;
return find(ver,t[rt].fa);
}
int main()
{
scanf("%d%d",&n,&m);
build(root[0],1,n);
for(int i=1,op,a,b;i<=m;++i)
{
scanf("%d",&op);
if(op==1)
{
root[i]=root[i-1];
scanf("%d%d",&a,&b);
int x=find(root[i],a);
int y=find(root[i],b);
if(t[x].fa==t[y].fa) continue;
if(t[x].dep>t[y].dep) swap(x,y);
change(root[i],root[i-1],1,n,t[x].fa,t[y].fa);
if(t[x].dep==t[y].dep) add(root[i],1,n,t[y].fa);
}
else if(op==2)
{
scanf("%d",&a);
root[i]=root[a];
}
else
{
root[i]=root[i-1];
scanf("%d%d",&a,&b);
int x=find(root[i],a);
int y=find(root[i],b);
puts(t[x].fa==t[y].fa?"1":"0");
}
}
return 0;
}