$3-$边连通分量的另一种算法

前置技能:图论概念梳理

众所周知,$3-$边连通分量有很多算法,其中不乏很多线性的做法,如这里

不过那些算法可能会不太好理解,下面将给出一个不同于上文的做法,相对易于理解,且需要的引理也比较少。可惜它的时间复杂度是 $O \left( \left( \left| V \right| + \left| E \right| \right) \alpha \left( \left| V \right| \right) \right)$,不过在实际应用方面完全不逊于常规写法。


首先,一些相关的前置知识,如切边切边对切边等价的概念可以在图论概念梳理这里找到。

[WF2015]Tours 的定理 2,可以通过随机权值的方法在线性时间内构造 "切边等价" 关系的等价类 $E_1, E_2, \cdots, E_\kappa$。

(留坑待填)

一个 P6658 的实现:

#include <bits/stdc++.h>
#define EB emplace_back
#define ad(x) (((x - 1) ^ 1) + 1)
using std::cin;
using std::cout;

typedef unsigned long long u64;
typedef std::pair <u64, int> pui;
typedef std::vector <int> vector;
const int N = 500054, M = N * 2;

struct edge {
	int u, v;
	edge (int u0 = 0, int v0 = 0) : u(u0), v(v0) {}
} e[M];

int V, E, C, Es = 0;
int first[N], next[M], col[M];
int cnt = 0, p[N], id[N], low[N];
bool bridge[M], treeQ[M];
pui ew[M];
u64 weight[N];

inline void down(int &x, const int y) {x > y ? x = y : 0;}

inline void addedge(int u, int v) {
	e[++Es] = edge(u, v), next[Es] = first[u], first[u] = Es;
	e[++Es] = edge(v, u), next[Es] = first[v], first[v] = Es;
}

void dfs(int x) {
	int i, y;
	id[x] = low[x] = ++cnt;
	for (i = first[x]; i; i = next[i])
		if (!id[y = e[i].v]) {
			p[y] = i, treeQ[i] = treeQ[ad(i)] = true, dfs(y), down(low[x], low[y]);
			if (id[x] < low[y]) bridge[ad(i)] = bridge[i] = true;
		} else if ((p[x] - 1) ^ (i - 1) ^ 1)
			down(low[x], id[y]);
}

void dfs2(int x) {
	int i, y;
	for (i = first[x]; i; i = next[i])
		if (p[y = e[i].v] == i) dfs2(y), weight[x] ^= weight[y], ew[ad(i)].first = ew[i].first = weight[y];
}

namespace e3cc {
	typedef std::list <int> list;

	int n_e3cc = 0, p[N], deg[N], count[M];
	int na = 0, absQ[M];
	int nd = 0, deg2Q[N];
	list li[N];
	vector e3cc[N];

	int ancestor(int x) {return p[x] == x ? x : (p[x] = ancestor(p[x]));}

	void main() {
		int i, j, c, u, v, x, z[3], nz;
		for (i = 1; i <= Es; i += 2) if (!bridge[i]) ++count[col[i]];
		for (i = 1; i <= Es; i += 2) if (!bridge[i]) {
			++deg[e[i].u], ++deg[e[i].v];
			if (count[col[i]] == 1) absQ[na++] = i;
		}
		for (i = 1; i <= V; ++i) {
			li[i].EB(i), p[i] = i, assert(deg[i] != 1);
			if (deg[i] == 2) deg2Q[nd++] = i;
		}
		for (; ; )
			if (na) {
				i = absQ[--na], u = ancestor(e[i].u), v = ancestor(e[i].v);
				if (u == v) deg[u] -= 2;
				else p[v] = u, deg[u] += deg[v] - 2, li[u].splice(li[u].end(), li[v]);
				if (deg[u] == 2) deg2Q[nd++] = u;
			} else if (nd) {
				x = deg2Q[--nd];
				if (deg[x] != 2) continue;
				x = ancestor(x), nz = 0;
				for (int r : li[x])
					for (i = first[r]; i; i = next[i])
						if (!bridge[i] && ancestor(e[i].v) != x)
							z[nz++] = i;
				assert(nz == 2), u = e[i = *z].v, v = e[j = z[1]].v;
				assert(col[i] == col[j]), c = col[i];
				if (p[u] == p[v]) continue;
				e[i].u = e[ad(i)].v = v, next[i] = first[v], first[v] = i, p[x] = p[v];
				if (--count[c] == 1) absQ[na++] = i;
			} else break;
		for (i = 1; i <= V; ++i) if (!li[i].empty()) e3cc[n_e3cc++].assign(li[i].begin(), li[i].end());
	}
}

int main() {
	int i, u, v; u64 w; vector *ans = e3cc::e3cc;
	std::mt19937_64 gen((std::random_device())());
	std::ios::sync_with_stdio(false), cin.tie(NULL);
	cin >> V >> E;
	for (i = 1; i <= E; ++i) if (cin >> u >> v, u != v) addedge(u, v);
	for (i = 1; i <= V; ++i) if (!id[i]) dfs(i);
	for (i = 1; i <= Es; i += 2) if (!treeQ[i])
		ew[ad(i)].first = ew[i].first = w = gen(), weight[e[i].u] ^= w, weight[e[i].v] ^= w;
	for (i = 1; i <= V; ++i) if (!p[i]) dfs2(i);
	for (i = 1; i <= Es; ++i) ew[i].second = i;
	std::sort(ew + 1, ew + (Es + 1));
	for (i = 1; i <= Es; ++i) col[ew[i].second] = (i != 1 && ew[i].first == ew[i - 1].first ? C : ++C);
	e3cc::main();
	for (i = 0; i < e3cc::n_e3cc; ++i) std::sort(ans[i].begin(), ans[i].end());
	std::sort(ans, ans + e3cc::n_e3cc), cout << e3cc::n_e3cc << '\n';
	for (i = 0; i < e3cc::n_e3cc; ++i) {
		v = ans[i].size();
		for (u = 0; u < v; ++u) cout << ans[i][u] << (u == v - 1 ? '\n' : ' ');
	}
	return 0;
}