1 条题解
-
0
思路与算法
用迭代lowlink和边栈分解点双连通块。单边块中的边不在任何环;非平凡块若边数等于顶点数,则是简单环,每边恰在一个环;边数更多的块中每边至少在两个环。按块累计题目要求的两类边。
正确性说明
每个简单环完整属于唯一点双块。二连通块中各点度至少2,边数等于顶点数强制全为2,即简单环。若不是简单环,由耳分解可知每条边所在环都有额外耳或等价替代路径,从而可构成第二个不同简单环。因此块分类对应题意。
复杂度
每组读入、栈与数组初始化、DFS及各块边处理均O(N+M),空间O(N+M);多组遇0 0结束。
易错点
原题无重边无自环,不将此结论直接套用到其他协议。孤点不产生边块。深链用显式栈,避免递归爆栈;块顶点用时间戳去重。
参考代码(C++11)
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; while (cin >> n >> m && n) { vector<vector<pair<int, int>>> g(n); vector<pair<int, int>> edge(m); for (int i = 0; i < m; i++) { int a, b; cin >> a >> b; edge[i] = {a, b}; g[a].push_back({b, i}); g[b].push_back({a, i}); } vector<int> dfn(n), low(n), par(n, -1), pe(n, -1), cur(n), stackE, stamp(n); int timer = 0, mark = 0, bridges = 0, multi = 0; for (int root = 0; root < n; root++) if (!dfn[root]) { vector<int> st(1, root); dfn[root] = low[root] = ++timer; while (!st.empty()) { int u = st.back(); if (cur[u] < (int)g[u].size()) { auto e = g[u][cur[u]++]; if (e.second == pe[u]) continue; int v = e.first; if (!dfn[v]) { par[v] = u; pe[v] = e.second; stackE.push_back(e.second); dfn[v] = low[v] = ++timer; st.push_back(v); } else if (dfn[v] < dfn[u]) { low[u] = min(low[u], dfn[v]); stackE.push_back(e.second); } } else { st.pop_back(); if (par[u] >= 0) { int p = par[u]; low[p] = min(low[p], low[u]); if (low[u] >= dfn[p]) { int E = 0, V = 0, id; ++mark; do { id = stackE.back(); stackE.pop_back(); E++; for (int v : {edge[id].first, edge[id].second}) if (stamp[v] != mark) { stamp[v] = mark; V++; } } while (id != pe[u]); if (E == 1) bridges++; else if (E > V) multi += E; } } } } } cout << bridges << ' ' << multi << '\n'; } }
- 1