Задача вимагає знайти кількість компонент сильної зв'язності орієнтованого графа. Для цього використовуємо алгоритм Косарайю, який виконує два обходи в глибину:
виконати обхід у глибину на вхідному графі та запам'ятати вершини в порядку завершення їх обробки (у момент виходу з вершини);
побудувати обернений граф, змінивши напрямок кожного ребра на протилежний;
перебрати вершини у зворотному порядку завершення їх обробки та запустити обхід у глибину на оберненому графі з кожної вершини, яка ще не була відвідана. Кожен такий запуск обходить рівно одну компоненту сильної зв'язності.
Обернений граф має ті самі компоненти сильної зв'язності, що й початковий: якщо існують шляхи з в і з в , то після зміни напрямку ребер ці шляхи зберігаються, змінюється лише їх напрямок. Порядок обробки вершин у другому обході гарантує, що обхід у глибину не вийде за межі поточної компоненти: усі компоненти, до яких він міг би дістатися, на цей момент уже відвідані.
Відповіддю є кількість запусків обходу в глибину під час другого обходу.
Приклад
Графи, наведені в прикладах, зображено нижче. Компоненти сильної зв'язності виділено кольором.

Перший граф не містить циклів: повернутися до жодної вершини неможливо. Тому кожна вершина утворює окрему компоненту сильної зв'язності: , , .
У другому графі вершини , і лежать на циклі 1 \to 2 \to 3 \to 1 та утворюють одну компоненту \{1, 2, 3\}. Вершина недосяжна з вершини , тому вершини і утворюють окремі компоненти та . Отже, кожен із графів містить компоненти сильної зв'язності.
Просимулюємо роботу алгоритму на другому графі. Перший обхід у глибину з вершини проходить шлях 1 \to 2 \to 3, а потім з вершини — шлях 4 \to 5. Порядок завершення обробки вершин: top = (3, 2, 1, 5, 4). Після інвертування отримуємо (4, 5, 1, 2, 3).
Обернений граф містить ребра 2 \to 1, 3 \to 2, 1 \to 3 та 5 \to 4. Виконаємо другий обхід у порядку (4, 5, 1, 2, 3):
: вершина не має вихідних ребер в оберненому графі, компонента ;
: ребро 5 \to 4 веде до вже відвіданої вершини, компонента ;
: обхід шляху 1 \to 3 \to 2, компонента \{1, 3, 2\}.
Вершини і вже відвідано, тому нових запусків немає. Отримуємо c = 3 компоненти.
Зауважимо, що в оберненому графі вершина , яка належить іншій компоненті, досяжна з вершини . Проте вершина обробляється раніше вершини , тому на момент виклику вона вже відвідана і не потрапляє до компоненти вершини .
Реалізація алгоритму
Зберігатимемо вхідний граф у вигляді списку суміжності , а обернений граф — у вигляді списку суміжності . Масив позначає відвідані вершини. Масив зберігає вершини в порядку завершення їх обробки під час першого обходу в глибину.
vector\u003cvector\u003cint\u003e \u003e g, gr;
vector\u003cint\u003e used, top;Функція dfs1 виконує обхід у глибину на вхідному графі. Вершина додається в кінець масиву після того, як оброблено всі вершини, досяжні з неї, тобто в момент виходу з неї.
void dfs1(int v)
{
used[v] = 1;
for (int to : g[v])
if (!used[to]) dfs1(to);
top.push_back(v);
}Функція dfs2 виконує обхід у глибину на оберненому графі. Усі вершини, відвідані за один виклик з основної програми, утворюють одну компоненту сильної зв'язності.
void dfs2(int v)
{
used[v] = 1;
for (int to : gr[v])
if (!used[to]) dfs2(to);
}Основна частина програми. Зчитуємо вхідний граф. Кожне ребро a \to b додаємо до графа , а протилежне ребро b \to a — до оберненого графа .
scanf("%d %d", \u0026n, \u0026m);
g.resize(n + 1);
gr.resize(n + 1);
for(i = 1; i \u003c= m; i++)
{
scanf("%d %d", \u0026a, \u0026b);
g[a].push_back(b);
gr[b].push_back(a);
}Запускаємо обхід у глибину на вхідному графі з кожної вершини, яка ще не була відвідана. Після цього масив містить усі вершини графа в порядку завершення їх обробки.
used.resize(n+1);
for(i = 1; i \u003c= n; i++)
if (!used[i]) dfs1(i);Перед другим обходом обнулимо масив . Вершини оберненого графа потрібно розглядати у зворотному порядку завершення їх обробки, тобто масив потрібно переглянути від кінця до початку. Для зручності обернемо масив .
used.assign(n + 1, 0);
reverse(top.begin(), top.end());Змінна підраховує кількість компонент сильної зв'язності.
c = 0;Пройдемо масивом зліва направо. З кожної ще не відвіданої вершини запускаємо обхід у глибину dfs2 на оберненому графі. Він обходить рівно одну нову компоненту сильної зв'язності, тому збільшуємо на .
for (int v : top)
if (!used[v])
{
dfs2(v);
c++;
}Виводимо кількість компонент сильної зв'язності графа.
printf("%d\n",c);Реалізація мовою Java
import java.util.*;
public class Main
{
static ArrayList\u003cInteger\u003e[] g, gr;
static boolean used[];
static Vector\u003cInteger\u003e top = new Vector\u003cInteger\u003e();
static void dfs1(int v)
{
used[v] = true;
for(int i = 0; i \u003c g[v].size(); i++)
{
int to = g[v].get(i);
if (!used[to]) dfs1(to);
}
top.add(v);
}
static void dfs2(int v)
{
used[v] = true;
for(int i = 0; i \u003c gr[v].size(); i++)
{
int to = gr[v].get(i);
if (!used[to]) dfs2(to);
}
}
@SuppressWarnings("unchecked")
public static void main(String[] args)
{
Scanner con = new Scanner(System.in);
int n = con.nextInt();
int m = con.nextInt();
g = new ArrayList[n+1];
for(int i = 0; i \u003c= n; i++)
g[i] = new ArrayList\u003cInteger\u003e();
gr = new ArrayList[n+1];
for(int i = 0; i \u003c= n; i++)
gr[i] = new ArrayList\u003cInteger\u003e();
for (int i = 0; i \u003c m; i++)
{
int a = con.nextInt();
int b = con.nextInt();
g[a].add(b);
gr[b].add(a);
}
used = new boolean[n+1];
for(int i = 1; i \u003c= n; i++)
if (!used[i]) dfs1(i);
used = new boolean[n+1];
int c = 0;
for(int i = 1; i \u003c= n; i++)
{
int v = top.get(n-i);
if (!used[v])
{
dfs2(v);
c++;
}
}
System.out.println(c);
con.close();
}
}Реалізація мовою Python
Функція dfs1 виконує обхід у глибину на вхідному графі. Вершина додається в кінець списку після того, як оброблено всі вершини, досяжні з неї, тобто в момент виходу з неї.
def dfs1(v):
global g, used, top
used[v] = True
for to in g[v]:
if not used[to]: dfs1(to)
top.append(v)Функція dfs2 виконує обхід у глибину на оберненому графі. Усі вершини, відвідані за один виклик з основної програми, утворюють одну компоненту сильної зв'язності.
def dfs2(v):
global gr, used
used[v] = True
for to in gr[v]:
if not used[to]: dfs2(to)Основна частина програми. Зчитуємо вхідний граф. Кожне ребро a \to b додаємо до графа , а протилежне ребро b \to a — до оберненого графа .
n, m = map(int, input().split())
g = [[] for _ in range(n + 1)]
gr = [[] for _ in range(n + 1)]
for _ in range(m):
a, b = map(int, input().split())
g[a].append(b)
gr[b].append(a)Запускаємо обхід у глибину на вхідному графі з кожної вершини, яка ще не була відвідана. Після цього список містить усі вершини графа в порядку завершення їх обробки.
used = [False] * (n + 1)
top = []
for i in range(1, n + 1):
if not used[i]: dfs1(i)Перед другим обходом обнулимо список . Вершини оберненого графа потрібно розглядати у зворотному порядку завершення їх обробки, тому обернемо список .
used = [False] * (n + 1)
top.reverse()Змінна підраховує кількість компонент сильної зв'язності.
c = 0Пройдемо списком зліва направо. З кожної ще не відвіданої вершини запускаємо обхід у глибину dfs2 на оберненому графі. Він обходить рівно одну нову компоненту сильної зв'язності, тому збільшуємо на .
for v in top:
if not used[v]:
dfs2(v)
c += 1Виводимо кількість компонент сильної зв'язності графа.
print(c)