并查集的模板:
- 暂不多说。
这篇文章的内容是如何玩死并查集
并查集判断二分图暂时不写。
Chapter I 删除子树内节点
有 n n n 个学生去打比赛。这 n n n 个学生总是喜欢每个人独自一队(所以初始时也是这样),他们的教练需要执行以下操作:
-
操作1:对于 x , y x,y x,y 两个学生,如果他们俩不在同一个队伍里,就把他们两个的队伍合并为一起。
-
操作2:对于 x , y x,y x,y 两个学生,如果他们俩不在同一个队伍里,就把 x x x 踢出原来的队伍,加入 y y y 的队伍。
-
操作3:对于 x x x 这个学生,询问他所在的队伍人数。
这道题很显然有删除操作(把 x x x 踢出原来的队伍)。如果我们采用路径压缩的话,这道题会变得极其不方便(压缩之后的路径很难改),但是我们又想使用路径压缩优化,怎么办?
我们都知道并查集里每个点都有父亲节点,那我们再给每个点添一个母亲节点备份节点不就行了?
e.g.
设有个情况长这样:
我们建立备份(bk),只链接备份节点
然后我们在执行操作2的时候,就可以直接把本身节点连到对应队伍的对应备份节点了。
代码:
//预处理
for(int i=1;i<=n;i++)
{
Ma[i]=i; //i号点的当前备份
Fa[i]=i; //并查集固有的父亲节点
Cnt[i]=1; //i所在的队伍中队员个数
}
//操作1
fx=getFather(Ma[x]);
fy=getFather(Ma[y]);
if(fx!=fy)
{
Fa[fx]=fy;
Cnt[fy]+=Cnt[fx];
}
//操作2
fx=getFather(Ma[x]);
fy=getFather(Ma[y]);
if(fx!=fy)
{
Ma[x]=Ma[y];
Cnt[fx]--;
Cnt[fy]++;
}
//操作3
fx=getFather(Ma[x]);
return Cnt[fx];
Chapter II 启发式合并
按秩合并
众所周知,按秩合并的原理是:
并查集合并时,高度小的树合并到高度大的树里,得到的新树结果还是原高度大的树;
而如果高度大的树合并到高度小的树里,或者两棵高度相同的树,得到的新树结果比原高度大的树多1。
这影响了找祖宗函数的效率。如果树很深的话,并查集就寄了。
所以我们记录每个节点的树的高度,然后再合并的时候比较并且修改即可。
void Merge(int a, int b)//启发式合并节点a和节点b所在的集合
{
int x=find(a);
int y=find(b);
if(high[y]<high[x])
fa[y]=x;
else{
if(high[x]==high[y])
high[y]++;
fa[x]=y;
}
}
注意!
这个东西不能使用路径压缩!!!
不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩 {\color{red}\colorbox{white}{不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩}} 不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩
我们再来看一道题。
某公司在某地拥有 n n n 个互不连通的战火厂,这些战火厂十分的闭塞,过于独立。
现在这些厂子之间要修铁路来促进交通,关于修铁路有两种操作:
-
操作0,对于 u , v u,v u,v 两厂,修建一条连接这两个厂子的双向铁路
-
操作1,对于 u , v u,v u,v 两厂,询问这两个厂子在修建第几条铁路时会联通。假如在这之前没有联通,输出0
//初始化
for(i=1;i<=n;i++)Fa[i]=i,H[i]=1; //H[i]指i号点为根的树的高度。
//操作0
void Merge(int x,int y,int cnt) //cnt表示当前是第cnt次连边操作
{
int fx=fa(x);
int fy=fa(y);
if(fx!=fy)
{
if(high[fx]<high[fy])
swap(fx,fy);
if(high[fx]==high[fy])
high[fx]++;
fa[fy]=fx;
Tim[fy]=cnt; //Tim[fy]记录fy集合与fx集合连接的时间
}
//本题的find
int getFa(int x)
{
if(fa[x]==x)return x;
int fx=find(fa[x]);
siz[x]=siz[fa[x]]+1; //siz[x]记录x所在树中的深度
return fx;
}
//操作1
int Query(int x,int y)
{
int ans=0;
int fx=find(x);
int fy=find(y);
if(fx!=fy)
return 0;
while(x!=y) //每次让两者深度较深的节点往上跳,最终一定相会在LCA处
{
if(siz[x]<siz[y])
swap(x,y);//合并与深度不能使用同一个数组
ans=max(ans,Tim[x]);
x=fa[x];
}
return ans;
}
Chapter III 撤销并查集
还是我们熟悉的题,但是它变了。
这 n n n 个学生仍然去打比赛。他们不听教练的话,还是喜欢每个人独自一队(所以初始情况又变成了这样),所以他们的教练需要重新分队。教练会执行以下操作:
-
操作1:对于 x , y x,y x,y 两个学生,如果他们俩不在同一个队伍里,就把他们两个的队伍合并为一起,并且我们称这次操作有效。
-
操作2:教练看破红尘,撤销最近的一次有效操作。
-
操作3:对于 x x x 这个学生,询问他所在的队伍人数。
我们用一个栈记录有效操作中被合并的那支队伍的最终祖宗(也就是最终父亲)。在撤销的时候,我们提取出栈顶元素,然后逆向操作一下即可(作者语文水平太差,看代码罢)
stack<int>Stack;//撤销操作记录
//并查集启发式合并
{ //Size[x]记录x所在并查集的元素个数
int fx=getFa(x),fy=getFa(y);
if(fx==fy)
return ;
if(Size[fx]>Size[fy])
swap(fx,fy);
Fa[fx]=fy;
Size[fy]+=Size[fx];
Stack.push(fx); //用于处理撤回
//撤销
x=Stack.top();
Stack.pop();
Size[Fa[x]]-=Size[x];
Fa[x]=x;
同样的,通过分析撤销操作代码可得,这题仍然 不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩 {\color{red}\colorbox{white}{不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩}} 不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩不能使用路径压缩
Chapter IV 并查集压缩树上路径
例题:
某国有
n
n
n 个城市,城市之间有一些双向道路相连,并且城市两两
之间有唯一路径。
现在有火车在城市 a a a ,需要经过 m m m 个城市。
火车按照以下规则行驶:每次行驶到还没有经过的城市中在 m m m 个城市中最靠前的。
现在小A想知道火车经过这 m m m 个城市后所经过的道路数量。
很多人看到这个题立马剖开了小区门口的大树,然而我们需要使用并查集解决这个题。以下是思路。
任意两点 A , B A,B A,B的距离为 d e p A + d e p B − 2 × d e p L c a A , B dep_A+dep_B-2\times dep_{Lca_{A,B}} depA+depB−2×depLcaA,B
树上一条路径经过的点是一段一段的,用并查集将一段路径合成一个点,每个点最多只能被合一次,时间复杂度O(n)。
初始化时:
对于没有指定要经过的点,在并查集中的父亲设为它在树中的父亲,即fa[i]=Ba[i];
对于指定要经过的点,在并查集中的父亲设为它自己,即fa[i]=i;
在Lca操作时,在A,B到Lca路径上,遇到没被访问过的点,把这个点并到父亲所在并查集。
void GoUp(int x,int lca)
{
if(dep[x]<dep[lca]) return;
mark[x]=0; //标记为已被经过
fa[x]=Ba[x];
GoUp(getFather(Ba[x]),lca);
}
void dfs(int x)
{ //初始时,m个指定点的mark值为1,其它点mark值为0
if(mark[x]==0)fa[x]=Ba[x]; else fa[x]=x;
for(int p=Last[x];p;p=Next[p])
if (End[p]!=Ba[x])
{
Ba[End[p]]=x;
dep[End[p]]=dep[x]+1;
dfs(End[p]);
}
}
本质是把经过的所有点都合并到同一个并查集里。