可持久化并查集
相信所有人都学过可持久化线段树 & 并查集吧(没学过的请出门右转,因为我太懒了没写这两篇)。
好吧没学过并查集的点这里。
正片开始
【模板】可持久化并查集
大概就是说给你 \(n\) 个点,需要进行 \(m\) 次以下操作:
- 合并 \(a\) 所在的集合与 \(b\) 所在的集合;
- 回到版本 \(k\),也就是第 \(k\) 次操作后的版本;
- 查询 \(a\) 与 \(b\) 是否在一个集合内;
抛开操作二不谈,这就是一个普普通通的并查集板子。但现在加上了这个神人操作,我们应该怎么办呢?
很容易联想到可持久化线段树,那么把这玩意和并查集合起来就成了。
具体来说,并查集最重要的就是 \(father\) 数组,于是乎我们需要将这东西可持久化。
那就用上刚刚提到的可持久化线段树了。
考虑时间复杂度:不加优化很显然是慢得爆炸的。
如何优化呢?
路径压缩绝对不行,不信可以试试看,会 \(T\) 的飞起。因为路径压缩在普通并查集中的复杂度是均摊的,由于某些神秘的原因,在可持久化并查集中它有可能会退化。
那用啥呢?
那就不得不提起另一个东西了——按秩合并。
顾名思义,指的就是“按照一定的秩序进行合并操作”(但我以前一直以为是“暗质合并”)。
有以下几个选项:
- 按照子树深度;
- 按照子树大小;
其实可以随机,但是随机被卡了,所以我们考虑使用以上两种之一。
其实很好理解,你考虑把大小或者深度更小的子树合并到大子树上去,肯定比反过来更优(可以感性理解一下)。
我喜欢按子树深度合并,当把深度小的合并到深度大的上面时,很容易发现深度要么保持不变要么就加一。
思想很简单,代码很复杂。
\(Code\)
在此声明:代码全是我手打的,旁边的 \(zyx\) 貌似认为我的代码很人机。
#include<bits/stdc++.h>
using namespace std;
char *p1,*p2,buf[100000];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
int read(){
int x=0,f=1;
char ch=nc();
while(ch<48||ch>57){
if(ch=='-') f=-1;
ch=nc();
}
while(ch>=48&&ch<=57) x=x*10+ch-48,ch=nc();
return x*f;
}
void write(long long x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
putchar('\n');
return;
} // 快读快写的板子
struct node{
int left_son,right_son,father,deep;
};
node tree[6000005];
int n,m,cnt=0,root[300005];
int build(int L,int R){
int tot=++cnt; // 创建新版本
if(L==R){
tree[tot].father=L,tree[tot].deep=1;
return tot;
}
int mid=(L+R)/2; // 左右两边继续
tree[tot].left_son=build(L,mid),tree[tot].right_son=build(mid+1,R);
return tot;
}
int query(int now,int L,int R,int u){
while(L!=R){
int mid=(L+R)/2;
if(u<=mid) now=tree[now].left_son,R=mid;
else now=tree[now].right_son,L=mid+1;
}
return now;
}
int find(int now,int u){
while(1){
int now_father=query(root[now],1,n,u); // 查找 u 的父亲
if(tree[now_father].father==u) return now_father; // 找到了
u=tree[now_father].father;
}
}
int copy(int now){ // 拷贝过来
int tot=++cnt;
tree[tot]=tree[now];
return tot;
}
int merge(int now,int L,int R,int pos,int pos_father){
int tot=copy(now);
if(L==R){
tree[tot].father=pos_father; // 小小合并
return tot;
}
int mid=(L+R)/2;
if(pos<=mid) tree[tot].left_son=merge(tree[now].left_son,L,mid,pos,pos_father);
else tree[tot].right_son=merge(tree[now].right_son,mid+1,R,pos,pos_father);
return tot;
}
void change(int now,int a,int b){
root[now]=root[now-1]; // 创建新版本
a=find(now,a),b=find(now,b); // 找俩父亲
if(tree[a].father!=tree[b].father){ // 不在一个集合里
if(tree[a].deep>tree[b].deep) swap(a,b); //深度小的合并到深度大的上
root[now]=merge(root[now-1],1,n,tree[a].father,tree[b].father);
if(tree[a].deep==tree[b].deep){ // 深度相同合并后要更新
int pos=tree[b].father;
root[now]=merge(root[now],1,n,pos,pos); // 复制节点
int _new=query(root[now],1,n,pos);
tree[_new].deep++;
}
}
}
bool decision(int now,int a,int b){ // 判断
a=find(now,a),b=find(now,b);
if(tree[a].father==tree[b].father) return true;
else return false;
}
int main(){
n=read(),m=read();
root[0]=build(1,n);
for(int i=1;i<=m;i++){
int opt;
opt=read();
if(opt==2){
int k=read();
root[i]=root[k]; // ban过来
}
else if(opt==1){
int a=read(),b=read();
change(i,a,b); // 修改
}
else{
int a=read(),b=read();
write(decision(i-1,a,b));
root[i]=root[i-1]; // 查询一次也算操作,要建新版本哦喵
}
}
return 0;
}
\(-\) ❀ 完结撒花 ❀ \(-\)

浙公网安备 33010602011771号