E030. Поиск точек сочленения

e-maxx algorithm original: C/C++ #algorithm #connectivity #emaxx #graph #search
题目文本会按所选界面语言从俄语翻译;代码保持不变。

Источник: e-maxx.ru/algo, страница PDF 94.

Пусть дан связный неориентированный 图. Точкой сочленения (или точкой артикуляции, англ. "cut vertex" или "articulation point") называется такая vertex, удаление которой делает 图 несвязным.

Опишем 算法, основанный на поиске в глубину, работающий за

, где

— количество вершин,

— рёбер.

算法

Запустим обход в глубину из произвольной вершины 图а; обозначим её через

. Заметим следующий

факт (который несложно доказать):

● Пусть мы находимся в обходе в глубину, просматривая сейчас все рёбра из вершины

. Тогда, если

текущее edge

таково, что из вершины

и из любого её потомка в дереве обхода в глубину нет обратного

ребра в или какого-либо предка вершины

, то vertex

является точкой сочленения. В противном случае, т.е.

если обход в глубину просмотрел все рёбра из вершины

, и не нашёл удовлетворяющего вышеописанным

условиям ребра, то vertex

не является точкой сочленения. (В самом деле, мы этим 题意м проверяем, нет

ли другого пути из

в

)

● Рассмотрим теперь оставшийся случай:

. Тогда эта vertex является точкой сочленения тогда и только

тогда, когда эта vertex имеет более одного сына в дереве обхода в глубину. (В самом деле, это означает, что, пройдя

из

по произвольному ребру, мы не смогли обойти весь 图, откуда сразу следует, что — точка сочленения). (Ср. формулировку этого критерия с формулировкой критерия для 算法а поиска мостов.) Теперь осталось научиться проверять этот факт для каждой вершины эффективно. Для этого воспользуемся "временами 输入а в вершину", вычисляемыми 算法ом поиска в глубину.

Итак, пусть

— это время захода поиска в глубину в вершину

. Теперь введём 数组

, который

и позволит нам отвечать на вышеописанные запросы. Время

равно минимуму из времени захода в саму

вершину

, времён захода в каждую вершину

, являющуюся концом некоторого обратного ребра

, а

также из всех значений

для каждой вершины

, являющейся непосредственным сыном

в дереве поиска: (здесь "back edge" — обратное edge, "tree edge" — edge дерева)

Тогда, из вершины

или её потомка есть обратное edge в её предка тогда и только тогда, когда найдётся такой сын

, что

.

Таким образом, если для текущего ребра

(принадлежащего дереву поиска) выполняется

,

то vertex

является точкой сочленения. Для начальной вершины

критерий другой: для этой вершины

надо посчитать number непосредственных сыновей в дереве обхода в глубину.

实现

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

сторону. Это, соответственно, случаи

,

,

и

. Таким образом, нам надо передавать в функцию поиска в глубину вершину-предка текущей вершины.

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++;

int children = 0;
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] && p != -1)

IS_CUTPOINT(v);

++children;

} }

if (p == -1 && children > 1)

IS_CUTPOINT(v);

}

int main() {
int n;

... чтение n и g ...

timer = 0;

for (int i=0; i<n; ++i)

used[i] = false;

dfs (0);

}

Здесь константе

должно быть заgiven значение, равное максимально возможному числу вершин во 输入ном 图е.

Функция

в коде — это некая функция, которая будет реагировать на то, что vertex

является точкой сочленения, на示例, выводить эту вершины на экран (надо учитывать, что для одной и той же вершины эта функция может быть вызвана несколько раз).

Задачи в online judges

Список задач, в которых it is required искать точки сочленения:

● UVA #10199 "Tourist Guide" [Complexity: низкая]

● UVA #315 "Network" [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.
    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++;
            int children = 0;
            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] && p != -1)
                                    IS_CUTPOINT(v);
                            ++children;
                    }
            }
            if (p == -1 && children > 1)
                    IS_CUTPOINT(v);
    }
    int main() {
            int n;
            ... чтение n и g ...
            timer = 0;
            for (int i=0; i<n; ++i)
                    used[i] = false;
            dfs (0);
    }
}

C++ 解法

匹配/原始
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++;
        int children = 0;
        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] && p != -1)
                                IS_CUTPOINT(v);
                        ++children;
                }
        }
        if (p == -1 && children > 1)
                IS_CUTPOINT(v);
}
int main() {
        int n;
        ... чтение n и g ...
        timer = 0;
        for (int i=0; i<n; ++i)
                used[i] = false;
        dfs (0);
}

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.
    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++;
            int children = 0;
            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] && p != -1)
                                    IS_CUTPOINT(v);
                            ++children;
                    }
            }
            if (p == -1 && children > 1)
                    IS_CUTPOINT(v);
    }
    int main() {
            int n;
            ... чтение n и g ...
            timer = 0;
            for (int i=0; i<n; ++i)
                    used[i] = false;
            dfs (0);
    }
}

Материал разбит как 算法ическая 题目: изучить постановку, понять асимптотику и реализовать 算法 на выбранном языке.

Vacancies for this task

活跃职位 with overlapping task tags are 已显示.

所有职位
目前还没有活跃职位。