1. 并查集基础:从连通性维护到关系拓展
作为一名算法工程师,我处理过大量需要维护元素连通性的场景。从社交网络的好友关系到电路板的连通检查,并查集(Disjoint Set Union)始终是我工具箱中的利器。今天我想系统梳理并查集的三种进阶用法,这些技巧曾帮我高效解决了许多复杂问题。
并查集本质上是一种树型数据结构,它擅长处理不相交集合的合并与查询。想象你正在管理一个大型公司的部门架构:当两个部门合并时,你需要快速确定某个员工属于哪个部门,这就是并查集的典型应用场景。
1.1 核心操作原理解析
标准并查集提供两个基本操作:
- Find:查找元素所属集合的代表元(通常称为"族长")
- Union:合并两个元素所在的集合
在代码实现时,我们使用一个父指针数组fa[]来表示森林结构。初始化时每个元素自成一派(fa[i] = i),合并操作通过修改父指针来实现集合归并。
cpp复制int fa[MAXN]; // 父节点数组
void init(int n) {
for(int i=1; i<=n; ++i)
fa[i] = i; // 初始各自为政
}
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]); // 路径压缩
}
void merge(int x, int y) {
fa[find(x)] = find(y); // 族长认亲
}
关键技巧:路径压缩通过在find过程中将节点直接指向根节点,将树高保持在接近常数的水平。这使得单次操作的时间复杂度接近O(α(n)),其中α是反阿克曼函数,增长极其缓慢。
1.2 实战应用:亲戚关系判断
考虑洛谷P1551亲戚关系问题。我们需要判断任意两人是否属于同一家族。这正是并查集的拿手好戏——将具有亲戚关系的两人合并到同一集合,查询时只需比较根节点是否相同。
cpp复制void solve() {
int n, m, p;
cin >> n >> m >> p;
init(n);
while(m--) {
int a, b;
cin >> a >> b;
