Мостом називається ребро, видалення якого збільшує кількість компонент зв'язності графа. Ребро є мостом тоді і тільки тоді, коли воно не лежить на жодному простому циклі: якщо ребро належить циклу, то після його видалення кінці ребра залишаються зв'язаними рештою циклу.
Запустимо пошук у глибину. Кожне ребро графа виявиться або ребром дерева пошуку, або зворотним ребром. Зворотне ребро разом зі шляхом по дереву між його кінцями утворює цикл, тому мостом воно бути не може. Отже, мостами можуть бути лише ребра дерева пошуку.
Під час пошуку для кожної вершини обчислимо дві мітки:
— час входу у вершину , тобто порядковий номер, під яким вершину було вперше відвідано;
— найменший час входу, якого можна досягти з вершини , спускаючись по ребрах дерева до її нащадків і потім пройшовши не більше ніж по одному зворотному ребру.
Розглянемо ребро дерева (v, to), де — син вершини . Можливі три випадки:
up[to] < d[v]. З деякої вершини піддерева веде зворотне ребро до власного предка вершини . Іншими словами, з вершини або її нащадка існує зворотне ребро до предка тоді і тільки тоді, коли у знайдеться такий син , що up[to] < d[v].
up[to] = d[v]. Піддерево містить зворотне ребро, яке веде рівно у вершину .
up[to] > d[v]. Жодна вершина піддерева не може досягти вершини або її предків, не проходячи по ребру (v, to). Отже, ребро (v, to) є мостом.
У перших двох випадках ребро (v, to) лежить на циклі і мостом не є. Таким чином, отримуємо критерій:
Теорема. Ребро дерева пошуку (v, to), де — син вершини , є мостом тоді і тільки тоді, коли up[to] > d[v].
Пошук у глибину виконується за O(n + m). Номери знайдених мостів сортуються перед виведенням.
Приклад
Граф, наведений у прикладі, має такий вигляд. Поряд з кожним ребром вказано його номер у вхідних даних:
Запустимо пошук у глибину з вершини . Сусідів кожної вершини будемо переглядати у порядку появи ребер у вхідних даних. Обхід відвідує вершини у порядку 1, 2, 3, 4, 5, 6. Ребрами дерева пошуку стають ребра 1, 2, 3, 5 і , а ребра 4 = (3, 1) і 6 = (6, 4) виявляються зворотними.
Перевіримо умову up[to] > d[v] для всіх ребер дерева:
Ребро (v, to) | Номер | Висновок | ||
1 | 1 | 1 | up[2] = d[1], не міст | |
2 | 1 | 2 | up[3] < d[2], не міст | |
3 | 4 | 3 | up[4] > d[3], міст | |
5 | 4 | 4 | up[5] = d[4], не міст | |
7 | 4 | 5 | up[6] < d[5], не міст |
Таким чином, у графі рівно один міст — ребро номер , що з'єднує вершини і .
Реалізація алгоритму
Граф зберігаємо у вигляді списку суміжності . Для кожної вершини у записані пари (to, id), де — сусідня вершина, а — номер ребра (v, to) у вхідних даних. Номер ребра потрібен з двох причин: щоб під час обходу не повертатися по тому самому ребру, по якому прийшли, і щоб виводити мости не як пари вершин, а як номери вхідних ребер.
Масиви і містять мітки і . Нульове значення означає, що вершина ще не відвідана. У масиві bridges збираємо номери ребер, які є мостами.
vector<vector<pair<int, int> > > g;
vector<int> d, up, bridges;Функція dfs реалізує пошук у глибину з вершини . Параметр — номер ребра, по якому ми прийшли у вершину . Для кореня дерева пошуку такого ребра немає, тому за замовчуванням p = -1. При вході у вершину присвоюємо і поточний час.
void dfs(int v, int p = -1)
{
d[v] = up[v] = timer++;Перебираємо всі ребра (v, to), що виходять з вершини . Ребро з номером пропускаємо: по ньому ми прийшли у , і воно веде до батька.
for (auto edge : g[v])
{
int to = edge.first;
int id = edge.second;
if (id == p) continue;Якщо вершина вже відвідана (d[to] \ne 0), то (v, to) — зворотне ребро. Перераховуємо з урахуванням часу входу в .
if (d[to] != 0)
up[v] = min(up[v], d[to]);Інакше (v, to) — ребро дерева. Запускаємо пошук з вершини , передаючи номер ребра . Після повернення перераховуємо через .
else
{
dfs(to, id);
up[v] = min(up[v], up[to]);Якщо up[to] > d[v], то з піддерева не можна потрапити у або її предків, минаючи ребро (v, to), тому ребро з номером є мостом.
if (up[to] > d[v]) bridges.push_back(id);
}
}
}Основна частина програми. Читаємо вхідний граф. Кожне ребро додаємо у списки суміжності обох вершин разом з його номером .
scanf("%d %d", &n, &m);
g.resize(n + 1);
for (i = 1; i <= m; i++)
{
scanf("%d %d", &a, &b);
g[a].push_back(make_pair(b, i));
g[b].push_back(make_pair(a, i));
}Ініціалізуємо масиви міток.
d.resize(n + 1);
up.resize(n + 1);Змінна зберігає поточний час пошуку.
timer = 1;Граф може бути незв'язним, тому запускаємо пошук у глибину з кожної ще не відвіданої вершини.
for (i = 1; i <= n; i++)
if (d[i] == 0) dfs(i);Сортуємо номери мостів за зростанням.
sort(bridges.begin(), bridges.end());Виводимо кількість мостів, а в наступному рядку — їхні номери.
printf("%d\n", bridges.size());
for (i = 0; i < bridges.size(); i++)
printf("%d ", bridges[i]);
printf("\n");