基础封装
通常意义上我们默认增加“路径压缩”优化,时间复杂度为:查询 O(1)\mathcal O (1) ,合并接近于 O(α(n))\mathcal O(\alpha(n)) (这里 α\alpha 代表的是反阿克曼函数,一般看作是一个极小的常数)。
struct DSU {vector<int> fa, p;DSU(int n) {fa.resize(n + 1);iota(fa.begin(), fa.end(), 0);p.resize(n + 1, 1);}int get(int x) {while (x != fa[x]) {x = fa[x] = fa[fa[x]];}return x;}bool merge(int x, int y) { // 设x是y的祖先x = get(x), y = get(y);if (x == y) return false;if (x < y) swap(x, y); // 将编号小的合并到大的上fa[y] = x;p[x] += p[y];return true;}bool same(int x, int y) {return get(x) == get(y);}int size(int x) { // 输出连通块中点的数量return p[get(x)];}
};
求解连通块内部点、边、环信息
并查集算法除了常规意义上的寻找(时间序意义上的)祖先节点、寻找块内最小/最大的节点之外,还可以用极小的时间代价维护连通块内点的数量、边的数量、是否存在自环等等内容。除了“路径压缩”优化外,还有“启发式合并”优化能够进一步缩短合并花费的时间,然而这会影响上述罗列的最大/最小节点查找,故一般我个人习惯不添加这一优化。
struct DSU {vector<int> fa, p, e, f;DSU(int n) {fa.resize(n + 1);iota(fa.begin(), fa.end(), 0);p.resize(n + 1, 1);e.resize(n + 1);f.resize(n + 1);}int get(int x) {while (x != fa[x]) {x = fa[x] = fa[fa[x]];}return x;}bool merge(int x, int y) { // 设x是y的祖先if (x == y) f[get(x)] = 1;x = get(x), y = get(y);e[x]++;if (x == y) return false;if (x < y) swap(x, y); // 将编号小的合并到大的上fa[y] = x;f[x] |= f[y], p[x] += p[y], e[x] += e[y];return true;}bool same(int x, int y) {return get(x) == get(y);}bool F(int x) { // 判断连通块内是否存在自环return f[get(x)];}int size(int x) { // 输出连通块中点的数量return p[get(x)];}int E(int x) { // 输出连通块中边的数量return e[get(x)];}
};
模板题
https://www.luogu.com.cn/problem/P3367