并查集用森林维护集合归属,f[x] 记 x 的父亲,根节点的父亲是自身。两种优化:路径压缩(查询时把路径上的点直接挂到根,常用)与启发式合并(小树并大树,带权并查集中常用)。
基本并查集(路径压缩)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const int N = 1e5 + 5; int f[N], n;
void init() { for (int i = 1; i <= n; i++) f[i] = i; }
int get(int x) { return f[x] == x ? x : f[x] = get(f[x]); }
void merge(int x, int y) { f[get(x)] = get(y); }
|
统计集合个数:数根节点个数,即 ans += (get(i) == i),集合数 = ans(连通分量还需建的路 = ans - 1)。
带权并查集(维护节点到根的信息)
把信息记在节点到父亲的边上,路径压缩时累加。下面以维护「到根的距离」、集合大小记在根上为例(P1196 银河英雄传说)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const int N = 30005; struct node { int fa, d, size; } f[N];
void init() { for (int i = 1; i <= N - 1; i++) f[i] = {i, 0, 1}; }
int get(int x) { if (f[x].fa == x) return x; int root = get(f[x].fa); f[x].d += f[f[x].fa].d; return f[x].fa = root; }
void merge(int x, int y) { int fx = get(x), fy = get(y); if (fx == fy) return; f[fy].d = f[fx].size; f[fy].fa = fx; f[fx].size += f[fy].size; }
|
拓展域并查集(维护多种关系)
把每个元素拆成多个域分别建并查集,空间 O(nk)(k 为域数)。以食物链为例:原域、捕食域、天敌域共 3 倍空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const int N = 5e4 + 5; int f[3 * N];
#define eat(x) (x + N) #define eaten(x) (x + 2 * N)
int get(int x) { return f[x] == x ? x : f[x] = get(f[x]); } void merge(int x, int y) { f[get(x)] = get(y); }
|
相关笔记:【数据结构】并查集 学习笔记