并查集
用途:
在近似于 O(1) 的时间内将两个集合合并或询问两个元素是否在一个集合中
基本思想:

每个集合用一棵树表示,用树根编号代表集合。对于每一个子节点都存储它的父节点 p[x]
来解决问题
- 判断两个元素是否属于同一个集合:
不断查找该节点的父节点 while (p[x] != x) x = p[x]
,直到 p[x] == x
为止,即查找到了根节点
- 合并两个集合:
只需将 1 集合的根节点连向集合 2 的根节点

此时我们发现,对于判断两个元素是否在同一集和的操作,分别需要将两个点遍历到根节点,造成时间复杂度极高
并查集优化 (路径压缩) :
当遍历一个元素到根节点的过程完成后,将该路径上的每一个点都直接指向根节点,对于下次查找该点以及路径上的其他点,都可以做到 O(1) 查找
代码模板
初始化
1 2 3 4
| int p[N];
for (int i = 1; i <= n; i ++) p[i] = i;
|
查找根节点
1 2 3 4 5 6 7 8 9 10 11
| int find(int x)
{ if (p[x] != x) p[x] = find(p[x]); return p[x]; }
|
并查集变种
- 动态维护每个集合中元素的个数
Acwing837-连通块中点的数量
用集合来维护一个连通块,当在两个数字间连一条边时,即将两个集合合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| const int N = 1e5 + 10;
int p[N]; int size[N];
int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; }
int main() { int n, m; cin >> n >> m; for (int i = 1; i <= n; i ++) { p[i] = i; size[i] = 1; } while (m --) { char op[2]; int a, b; cin >> op; if (op[0] == 'C') { cin >> a >> b; if (find(a) == find(b)) continue; size[find(b)] += size[find(a)]; p[find(a)] = find(b); } else if (op[1] == '1') { cin >> a >> b; if (find(a) == find(b)) cout << "Yes" << endl; else cout << "No" << endl; } else { cin >> a; cout << size[find(a)] << endl; } } }
|