前置技能:图论概念梳理。
众所周知,$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$。
(留坑待填)
#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;
}