Точки сочленения: почему у корня DFS особое условие (два и более ребёнка)?
Ищу точки сочленения (вершины, удаление которых увеличивает число компонент). Условие low[u] >= tin[v] для ребёнка u вроде понятно. Но для корня дерева DFS оно почему-то не работает — корень считается точкой сочленения, только если у него два ребёнка в дереве DFS. Почему корень — особый случай?
2 ответа
Вершина v (не корень) — точка сочленения, если у неё есть ребёнок u в дереве DFS, из поддерева которого нельзя добраться выше v: low[u] >= tin[v]. Тогда удаление v отрезает это поддерево.
Почему корень особый. У не-корня всегда есть «верх» (предки), и условие low[u] >= tin[v] означает «поддерево u связано с остальным графом только через v». Но у корня нет предков — единственный способ, которым его поддеревья связаны между собой, это сам корень. Если у корня в дереве DFS ≥ 2 детей, эти поддеревья не связаны напрямую (иначе DFS объединил бы их в одно поддерево, добравшись по ребру), значит удаление корня их разъединит — корень точка сочленения. Если ребёнок один — удаление корня оставляет граф связным.
int timer = 0;
vector<int> tin, low;
vector<bool> isCut;
vector<int> g[N];
void dfs(int v, int parent) {
tin[v] = low[v] = timer++;
int children = 0;
for (int u : g[v]) {
if (u == parent) continue; // здесь хватает вершины (см. ниже про кратные)
if (tin[u] != -1) {
low[v] = min(low[v], tin[u]);
} else {
dfs(u, v);
low[v] = min(low[v], low[u]);
if (parent != -1 && low[u] >= tin[v]) isCut[v] = true; // не-корень
children++;
}
}
if (parent == -1 && children >= 2) isCut[v] = true; // корень
}
Сложность O(V+E), память O(V). Обратите внимание: для не-корня условие нестрогое >= (в отличие от мостов, где строгое >), потому что вершина-сочленение может сама лежать на цикле и всё равно резать граф.
Грабля с кратными рёбрами тут мягче, чем у мостов: для точек сочленения бан по вершине-родителю (u == parent) обычно достаточен, потому что условие на low нестрогое и параллельное ребро в родителя не меняет факт «сочленение». Но если хотите единый код для мостов И точек сочленения — ведите бан по индексу ребра, это безопасно для обоих.
Ещё: одна вершина может быть помечена isCut[v]=true несколько раз (за разных детей) — это нормально, true идемпотентно. И не считайте детей через g[v].size() — нужно именно число древесных детей (тех, в кого реально ушли рекурсией), поэтому инкремент children только в ветке tin[u]==-1.