EduBrick

Мосты и точки сочленения

Одна величина up[v] решает обе задачи. Вывод формулы, отдельный случай корня и ловушка с кратными рёбрами.

4 мин

В неориентированном графе:

Мост — ребро, при удалении которого число компонент связности увеличивается.

Точка сочленения — вершина, при удалении которой (вместе с её рёбрами) число компонент увеличивается.

Наивно и то, и другое ищется перебором: удалить и пересчитать компоненты, O(m(n+m))O(m(n+m)) и O(n(n+m))O(n(n+m)). Обе задачи решаются одним обходом за O(n+m)O(n + m), причём почти одинаково.

Величина up

Подвесим дерево обхода и введём две величины для каждой вершины:

  • hvh_v — глубина вершины в дереве обхода;
  • upvup_vминимальная глубина, которой можно достичь, если из vv спуститься вниз по древесным рёбрам сколько угодно раз (в том числе ноль) и затем ровно один раз подняться по обратному ребру.

Рекуррентность прямо из определения. Из vv можно:

  • никуда не идти: получаем hvh_v;
  • сразу подняться по обратному ребру в uu: получаем huh_u;
  • спуститься в ребёнка toto и там повторить выбор: получаем uptoup_{to}.
upv=min(hv,  minобратное vuhu,  minребёнок toupto)up_v = \min\Bigl(h_v,\; \min_{\text{обратное } v \to u} h_u,\; \min_{\text{ребёнок } to} up_{to}\Bigr)

Обратите внимание на асимметрию: для обратных рёбер берётся huh_u, для детей — uptoup_{to}. Перепутать их — самая частая ошибка, и формулу стоит не заучивать, а уметь выводить из определения.

Критерии

Ребро vtov \to to (древесное) — мост, если upto>hvup_{to} > h_v.

Смысл: из поддерева toto нельзя подняться выше vv, даже спустившись как угодно глубоко. Значит, кроме этого ребра, никакого пути из поддерева наружу нет, и удаление его разрывает граф.

Если uptohvup_{to} \le h_v, то есть обходной путь, и ребро не мост.

Вершина vv (не корень) — точка сочленения, если у неё есть ребёнок toto с uptohvup_{to} \ge h_v.

Разница в одном знаке. Для моста нужно строго выше vv; для точки сочленения достаточно, чтобы поддерево не могло подняться выше самой vv — если максимум, куда оно дотягивается, это сама vv, то удаление vv отрезает поддерево.

Корень — точка сочленения, если у него больше одного ребёнка в дереве обхода.

Корень приходится разбирать отдельно: для него uptohrootup_{to} \ge h_{root} выполняется всегда, потому что hrooth_{root} — минимальная глубина. Без особого случая код объявил бы корень точкой сочленения в любом графе.

Считать нужно именно детей в дереве обхода, а не соседей: у корня может быть три соседа, но если все они в одном поддереве, корень не точка сочленения.

Код

vector<int> h, up;
set<pair<int, int>> bridges;
set<int> cutpoints;

void dfs(int v, int parent) {
    up[v] = h[v];
    int children = 0;
    bool skippedParent = false;
    for (int to : g[v]) {
        if (to == parent && !skippedParent) { skippedParent = true; continue; }
        if (h[to] != -1) {
            up[v] = min(up[v], h[to]);          // обратное ребро
        } else {
            h[to] = h[v] + 1;
            dfs(to, v);
            children++;
            up[v] = min(up[v], up[to]);         // ребёнок
            if (up[to] > h[v]) bridges.insert({min(v, to), max(v, to)});
            if (parent != -1 && up[to] >= h[v]) cutpoints.insert(v);
        }
    }
    if (parent == -1 && children > 1) cutpoints.insert(v);
}

// в main:
h.assign(n, -1);
up.assign(n, -1);
for (int v = 0; v < n; v++)
    if (h[v] == -1) { h[v] = 0; dfs(v, -1); }

Проверено: на двадцати тысячах случайных графов до восьми вершин множество мостов и множество точек сочленения совпали с прямым перебором — удалить ребро (вершину) и пересчитать компоненты.

Ловушка с кратными рёбрами

Флаг skippedParent — не украшение. Ребро в родителя нужно пропустить ровно один раз.

Если между vv и родителем два ребра, то это цикл длины 2, и ни одно из них не мост. Наивная проверка if (to == parent) continue; пропустит оба и объявит ребро мостом — неверно.

Надёжнее хранить не номер вершины-родителя, а номер ребра, по которому пришли, и пропускать именно его. Флаг выше — облегчённая версия того же приёма.

Петли на мосты и точки сочленения не влияют вовсе, но их стоит отфильтровать при чтении, чтобы не мешались.

Разница между двумя понятиями

Их легко перепутать, а связь между ними не такая прямая, как кажется.

Концы моста не обязаны быть точками сочленения: если мост — единственное ребро вершины степени 1, то удаление этой вершины ничего не разорвёт.

И наоборот, точка сочленения может не быть концом моста: две «восьмёрки», склеенные в одной вершине, дают точку сочленения без единого моста.

Поэтому вычислять одно через другое нельзя — но, к счастью, обе величины считаются одним и тем же обходом.