并查集
操作:
- 将两个集合合并
- 询问两个元素是否在一个集合中
暴力算法:合并$O(n)$,查询$O(1)$
并查集:
基本原理:每个集合用一棵树来表示,树根的编号就是整个集合的编号。每个节点用$fa$数组存储父节点
问题1:如何判断树根:fa[x]==x
问题2:如何查找树根:while(fa[x]!=x)x=fa[x]
问题3:如何合并两个集合:求出$x,y$两点所在树的根节点$fa_x,fa_y$,把其中一个根节点连到另一棵树的根节点上fa[x]=y
这样就实现了并查集的基本操作,但可以发现此时的复杂度依然可以达到$O(n)$,于是考虑优化
路径压缩优化:查找树根时更新父节点直到根节点

按秩合并:把深度较小的树连接到深度较大的树的根节点上,例:

可以发现,把深度较小的树连接到深度较大的树上,合并后的树的深度小,因为查询的复杂度与深度正相关,所以这样的复杂度较低。
可以使用$d$数组维护每个节点所在树的深度,实际实现中只需记录根节点的深度即可。
优化后复杂度近似为$O(1)$
定义及初始化:
1 | int fa[N],d[N],sz[N];//d,sz为深度和大小,是维护的额外信息 |
查找(路径压缩):
1 | int find(int x){ |
合并(按秩合并):
1 | void merge(int x,int y){ |
一般不使用按秩合并:
1 | void merge(int x,int y){ |
带权并查集
用每一个点到根节点的距离表示这个点与根节点的关系
$\left {\begin{matrix} dis \equiv 1 (\bmod 3) & 当前点被根吃\ dis \equiv 2 (\bmod 3) & 根被当前点吃 \ dis \equiv 0 (\bmod 3) & 当前点与根同类\end{matrix}\right.$