E029. Поиск мостов
Источник: e-maxx.ru/algo, страница PDF 92.
Пусть дан неориентированный 图. Мостом называется такое edge, удаление которого делает 图 несвязным (или, точнее, увеличивает number компонент связности). it is required find все мосты в заданном 图е. Неформально эта 题目 ставится следующим образом: it is required find на заданной карте дорог все "важные" дороги, т.е. такие дороги, что удаление любой из них приведёт к исчезновению пути между какой-то парой городов. Ниже мы опишем 算法, основанный на поиске в глубину, и работающий за время
, где
—
количество вершин,
— рёбер в 图е. Заметим, что на сайте также описан онлайновый 算法 поиска мостов — в отличие от описанного здесь 算法а, онлайновый 算法 умеет поддерживать все мосты 图а в изменяющемся 图е (имеются в виду добавления новых рёбер).
算法
Запустим обход в глубину из произвольной вершины 图а; обозначим её через
. Заметим следующий
факт (который несложно доказать):
● Пусть мы находимся в обходе в глубину, просматривая сейчас все рёбра из вершины
. Тогда, если текущее
edge
таково, что из вершины
и из любого её потомка в дереве обхода в глубину нет обратного ребра
в вершину
или какого-либо её предка, то это edge является мостом. В противном случае оно мостом не является. (В самом деле, мы этим 题意м проверяем, нет ли другого пути из
в
, кроме как спуск по ребру
дерева обхода в глубину.)
Теперь осталось научиться проверять этот факт для каждой вершины эффективно. Для этого воспользуемся "временами 输入а в вершину", вычисляемыми 算法ом поиска в глубину.
Итак, пусть
— это время захода поиска в глубину в вершину
. Теперь введём 数组
, который
и позволит нам отвечать на вышеописанные запросы. Время
равно минимуму из времени захода в саму
вершину
, времён захода в каждую вершину
, являющуюся концом некоторого обратного ребра
, а
также из всех значений
для каждой вершины
, являющейся непосредственным сыном
в дереве поиска: (здесь "back edge" — обратное edge, "tree edge" — edge дерева)
Тогда, из вершины
или её потомка есть обратное edge в её предка тогда и только тогда, когда найдётся такой сын
, что
. (Если
, то это означает, что найдётся обратное edge,
приходящее точно в
; если же
, то это означает наличие обратного ребра в какого-либо
предка вершины
.)
Таким образом, если для текущего ребра
(принадлежащего дереву поиска) выполняется
,
то это edge является мостом; в противном случае оно мостом не является.
实现
Если говорить о самой реализации, то здесь нам нужно уметь различать три случая: когда мы идём по ребру дерева поиска в глубину, когда идём по обратному ребру, и когда пытаемся пойти по ребру дерева в обратную сторону. Это, соответственно, случаи:
●
— критерий ребра дерева поиска;
●
— критерий обратного ребра;
●
— критерий прохода по ребру дерева поиска в обратную сторону. Таким образом, для реализации этих критериев нам надо передавать в функцию поиска в глубину вершину-предка текущей вершины.
const int MAXN = ...;
vector<int> g[MAXN];
bool used[MAXN];
int timer, tin[MAXN], fup[MAXN];
void dfs (int v, int p = -1) {
used[v] = true;
tin[v] = fup[v] = timer++;
for (size_t i=0; i<g[v].size(); ++i) {
int to = g[v][i];
if (to == p) continue;
if (used[to])
fup[v] = min (fup[v], tin[to]);
else {
dfs (to, v);
fup[v] = min (fup[v], fup[to]);
if (fup[to] > tin[v])
IS_BRIDGE(v,to);
} } }
void find_bridges() {
timer = 0;
for (int i=0; i<n; ++i)
used[i] = false;
for (int i=0; i<n; ++i)
if (!used[i])
dfs (i);
}
Здесь основная функция для вызова — это
— она производит необходимую инициализацию и
запуск обхода в глубину для каждой компоненты связности 图а.
При этом
— это некая функция, которая будет реагировать на то, что edge
является мостом, на示例, выводить это edge на экран.
Константе
в самом начале кода следует задать значение, равное максимально возможному числу вершин во 输入ном 图е. Стоит заметить, что эта 实现 некорректно работает при наличии в 图е кратных рёбер: она фактически не обращает внимания, кратное ли edge или оно единственно. Разумеется, кратные рёбра не должны 输入ить в
ответ, поэтому при вызове
можно проверять дополнительно, не кратное ли edge мы хотим добавить в ответ. Другой способ — более аккуратная работа с предками, т.е. передавать в
не вершину-предка, а номер
ребра, по которому мы вошли в вершину (для этого надо будет дополнительно хранить номера всех рёбер).
Задачи в online judges
Список задач, в которых it is required искать мосты:
● UVA #796 "Critical Links" [Complexity: низкая]
● UVA #610 "Street Directions" [Complexity: средняя]
C# 解法
自动草稿,提交前请检查using System;
using System.Collections.Generic;
using System.Linq;
public static class AlgorithmDraft
{
// Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
const int MAXN = ...;
List<int> g[MAXN];
bool[] used = new bool[MAXN];
int timer, tin[MAXN], fup[MAXN];
void dfs (int v, int p = -1) {
used[v] = true;
tin[v] = fup[v] = timer++;
for (size_t i=0; i<g[v].size(); ++i) {
int to = g[v][i];
if (to == p) continue;
if (used[to])
fup[v] = min (fup[v], tin[to]);
else {
dfs (to, v);
fup[v] = min (fup[v], fup[to]);
if (fup[to] > tin[v])
IS_BRIDGE(v,to);
}
}
}
void find_bridges() {
timer = 0;
for (int i=0; i<n; ++i)
used[i] = false;
for (int i=0; i<n; ++i)
if (!used[i])
dfs (i);
}
}
C++ 解法
匹配/原始const int MAXN = ...;
vector<int> g[MAXN];
bool used[MAXN];
int timer, tin[MAXN], fup[MAXN];
void dfs (int v, int p = -1) {
used[v] = true;
tin[v] = fup[v] = timer++;
for (size_t i=0; i<g[v].size(); ++i) {
int to = g[v][i];
if (to == p) continue;
if (used[to])
fup[v] = min (fup[v], tin[to]);
else {
dfs (to, v);
fup[v] = min (fup[v], fup[to]);
if (fup[to] > tin[v])
IS_BRIDGE(v,to);
}
}
}
void find_bridges() {
timer = 0;
for (int i=0; i<n; ++i)
used[i] = false;
for (int i=0; i<n; ++i)
if (!used[i])
dfs (i);
}
Java 解法
自动草稿,提交前请检查import java.util.*;
import java.math.*;
public class AlgorithmDraft {
// Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
const int MAXN = ...;
ArrayList<Integer> g[MAXN];
boolean used[MAXN];
int timer, tin[MAXN], fup[MAXN];
void dfs (int v, int p = -1) {
used[v] = true;
tin[v] = fup[v] = timer++;
for (size_t i=0; i<g[v].size(); ++i) {
int to = g[v][i];
if (to == p) continue;
if (used[to])
fup[v] = min (fup[v], tin[to]);
else {
dfs (to, v);
fup[v] = min (fup[v], fup[to]);
if (fup[to] > tin[v])
IS_BRIDGE(v,to);
}
}
}
void find_bridges() {
timer = 0;
for (int i=0; i<n; ++i)
used[i] = false;
for (int i=0; i<n; ++i)
if (!used[i])
dfs (i);
}
}
Материал разбит как 算法ическая 题目: изучить постановку, понять асимптотику и реализовать 算法 на выбранном языке.
Vacancies for this task
活跃职位 with overlapping task tags are 已显示.