diff --git a/doc/README.md b/doc/README.md index 755f421..3f3d2ea 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,6 +1,6 @@ # Теория по алгоритмам: -## 1. Обходы гррафа +## 1. Обходы графа ### 1.1. Ориентированный граф, псевдограф. Неориентированный граф, псевдограф. Связность в неор. графе, компоненты связности. Слабая и сильная связность в ор. графе. Компоненты слабой, сильной связности. ### 1.2. Обход в глубину. Цвета вершин. Времена входа и выхода. Лемма о белых путях(с доказательством). ### 1.3. Проверка связности неориентированного графа. Поиск цикла в неориентированном и ориентированном графе. Топологическая сортировка. diff --git a/doc/theory.md b/doc/theory.md new file mode 100644 index 0000000..72ebf4d --- /dev/null +++ b/doc/theory.md @@ -0,0 +1,1347 @@ +# Теория по алгоритмам + +- [1. Обходы графа](#1-обходы-графа) + - [1.1. Ориентированный граф, псевдограф. Неориентированный граф, псевдограф. Связность в неориентированном графе, компоненты связности. Слабая и сильная связность в ориентированном графе. Компоненты слабой, сильной связности](#11-ориентированный-граф-псевдограф-неориентированный-граф-псевдограф-связность-в-неориентированном-графе-компоненты-связности-слабая-и-сильная-связность-в-ориентированном-графе-компоненты-слабой-сильной-связности) + - [Ориентированный граф](#ориентированный-граф) + - [Неориентированный граф](#неориентированный-граф) + - [1.2. Обход в глубину. Цвета вершин. Времена входа и выхода. Лемма о белых путях (с доказательством)](#12-обход-в-глубину-цвета-вершин-времена-входа-и-выхода-лемма-о-белых-путях-с-доказательством) + - [Обход в глубину](#обход-в-глубину) + - [Лемма о белых путях](#лемма-о-белых-путях) + - [1.3. Проверка связности неориентированного графа. Поиск цикла в неориентированном и ориентированном графе. Топологическая сортировка](#13-проверка-связности-неориентированного-графа-поиск-цикла-в-неориентированном-и-ориентированном-графе-топологическая-сортировка) + - [Проверка связности неориентированного графа](#проверка-связности-неориентированного-графа) + - [Поиск цикла в неориентированном и ориентированном графе](#поиск-цикла-в-неориентированном-и-ориентированном-графе) + - [Топологическая сортировка](#топологическая-сортировка) + - [1.4. Нахождение компонент сильной связности. Алгоритм Косарайю с доказательством корректности(концепция). Алгоритм Тарьяна без доказательства корректности](#14-нахождение-компонент-сильной-связности-алгоритм-косарайю-с-доказательством-корректностиконцепция-алгоритм-тарьяна-без-доказательства-корректности) + - [Нахождение компонент сильной связности](#нахождение-компонент-сильной-связности) + - [Алгоритм Косарайю с доказательством корректности](#алгоритм-косарайю-с-доказательством-корректности) + - [Алгоритм Тарьяна без доказательства корректности](#алгоритм-тарьяна-без-доказательства-корректности) + - [1.5. Компоненты реберной двусвязности. Мосты. Поиск мостов](#15-компоненты-реберной-двусвязности-мосты-поиск-мостов) + - [Компоненты реберной двусвязности](#компоненты-реберной-двусвязности) + - [Мосты](#мосты) + - [Поиск мостов](#поиск-мостов) + - [1.6. Компоненты вершинной двусвязности. Точки сочленения. Поиск точек сочленения](#16-компоненты-вершинной-двусвязности-точки-сочленения-поиск-точек-сочленения) + - [Компоненты вершинной двусвязности](#компоненты-вершинной-двусвязности) + - [Точки сочленения](#точки-сочленения) + - [Поиск точек сочленения](#поиск-точек-сочленения) + - [1.7. Волновой алгоритм. Обход в ширину (применение очереди в волновом алгоритме)](#17-волновой-алгоритм-обход-в-ширину-применение-очереди-в-волновом-алгоритме) + - [Волновой алгоритм](#волновой-алгоритм) + - [Обход в ширину (применение очереди в волновом алгоритме)](#обход-в-ширину-применение-очереди-в-волновом-алгоритме) + - [1.8. Критерий существования Эйлерова пути и цикла в ориентированном и неориентированном графе. Поиск эйлерова пути и цикла](#18-критерий-существования-эйлерова-пути-и-цикла-в-ориентированном-и-неориентированном-графе-поиск-эйлерова-пути-и-цикла) + - [Критерий существования Эйлерова пути и цикла в неориентированном графе](#критерий-существования-эйлерова-пути-и-цикла-в-неориентированном-графе) + - [Критерий существования Эйлерова пути и цикла в ориентированном графе](#критерий-существования-эйлерова-пути-и-цикла-в-ориентированном-графе) + - [Поиск эйлерова пути и цикла](#поиск-эйлерова-пути-и-цикла) +- [2. Планарность графа](#2-планарность-графа) + - [2.1. Формула Эйлера. Теорема Портнягина-Куратовского. Теорема Вагнера](#21-формула-эйлера-теорема-портнягина-куратовского-теорема-вагнера) + - [Формула Эйлера](#формула-эйлера) + - [Теорема Портнягина-Куратовского](#теорема-портнягина-куратовского) + - [Теорема Вагнера](#теорема-вагнера) + - [2.2. Гамма алгоритм. Контактная вершина](#22-гамма-алгоритм-контактная-вершина) + - [Гамма алгоритм](#гамма-алгоритм) + - [2.3. Теорема о корректности Гамма алгоритма. Асимптотика алгоритма](#23-теорема-о-корректности-гамма-алгоритма-асимптотика-алгоритма) + - [Теорема о корректности Гамма алгоритма](#теорема-о-корректности-гамма-алгоритма) +- [3. Кратчайшие пути во взвешенном графе](#3-кратчайшие-пути-во-взвешенном-графе) + - [3.1. Алгоритм Дейкстры. Доказательство корректности(с доказательством). Оценка времени работы. Дерево кратчайших путей](#31-алгоритм-дейкстры-доказательство-корректностис-доказательством-оценка-времени-работы-дерево-кратчайших-путей) + - [Алгоритм Дейкстры](#алгоритм-дейкстры) + - [Доказательство корректности](#доказательство-корректности) + - [Оценка времени работы](#оценка-времени-работы) + - [Дерево кратчайших путей](#дерево-кратчайших-путей) + - [3.2. Потенциалы. Условие применимости алгоритма Дейкстры для измененных длин ребер. Потенциал $\\pi(v) = \\rho(v, t)$](#32-потенциалы-условие-применимости-алгоритма-дейкстры-для-измененных-длин-ребер-потенциал-piv--rhov-t) + - [Потенциалы](#потенциалы) + - [Условие применимости алгоритма Дейкстры для измененных длин ребер](#условие-применимости-алгоритма-дейкстры-для-измененных-длин-ребер) + - [Потенциал $\\pi(v) = \\rho(v, t)$](#потенциал-piv--rhov-t) + - [3.3. Алгоритм A\*. Условие монотонности на эвристику. Примеры эвристик](#33-алгоритм-a-условие-монотонности-на-эвристику-примеры-эвристик) + - [Алгоритм A\*](#алгоритм-a) + - [Примеры эвристик](#примеры-эвристик) + - [3.4. Алгоритм Форда-Беллмана. Хранение в матрице: $D\_{vk}$ равно длине кратчайшего пути до вершины $v$ за ровно $k$ ребер (не более $k$ ребер). Доказательство корректности (полное). Оценка времени работы](#34-алгоритм-форда-беллмана-хранение-в-матрице-d_vk-равно-длине-кратчайшего-пути-до-вершины-v-за-ровно-k-ребер-не-более-k-ребер-доказательство-корректности-полное-оценка-времени-работы) + - [Доказательство корректности алгоритма Форда-Беллмана](#доказательство-корректности-алгоритма-форда-беллмана) + - [Оценка времени работы алгоритма Форда-Беллмана](#оценка-времени-работы-алгоритма-форда-беллмана) + - [3.5. Восстановление пути. Детектирование цикла отрицательного веса. Поиск самого цикла](#35-восстановление-пути-детектирование-цикла-отрицательного-веса-поиск-самого-цикла) + - [Восстановление пути](#восстановление-пути) + - [Детектирование цикла отрицательного веса](#детектирование-цикла-отрицательного-веса) + - [Поиск самого цикла](#поиск-самого-цикла) + - [3.6. Нахождение кратчайших путей с учетом циклов отрицательного веса](#36-нахождение-кратчайших-путей-с-учетом-циклов-отрицательного-веса) + - [3.7. Алгоритм Флойда. Доказательство (концепция). Восстановление пути](#37-алгоритм-флойда-доказательство-концепция-восстановление-пути) + - [Алгоритм Флойда](#алгоритм-флойда) + - [Доказательство (концепция)](#доказательство-концепция) + - [Восстановление пути в алгоритме Флойда](#восстановление-пути-в-алгоритме-флойда) + - [3.8. Нахождение цикла отрицательного веса](#38-нахождение-цикла-отрицательного-веса) + - [3.9. Алгоритм Джонсона. Добавление фиктивного корня и фиктивных ребер для запуска алгоритма Форда-Беллмана](#39-алгоритм-джонсона-добавление-фиктивного-корня-и-фиктивных-ребер-для-запуска-алгоритма-форда-беллмана) + - [Алгоритм Джонсона](#алгоритм-джонсона) + - [Добавление фиктивного корня и фиктивных ребер для запуска алгоритма Форда-Беллмана](#добавление-фиктивного-корня-и-фиктивных-ребер-для-запуска-алгоритма-форда-беллмана) +- [4. Остовные деревья](#4-остовные-деревья) + - [4.1. Остовное дерево. Построение с помощью обхода в глубину и в ширину](#41-остовное-дерево-построение-с-помощью-обхода-в-глубину-и-в-ширину) + - [Остовное дерево](#остовное-дерево) + - [Построение с помощью обхода в глубину и в ширину](#построение-с-помощью-обхода-в-глубину-и-в-ширину) + - [4.2. Определение минимального остовного дерева](#42-определение-минимального-остовного-дерева) + - [4.3. Теорема о разрезе. Доказательство](#43-теорема-о-разрезе-доказательство) + - [Теорема о разрезе](#теорема-о-разрезе) + - [4.4. Алгоритм Прима. Аналогия с алгоритмом Дейкстры. Оценка времени работы для различных реализаций очереди с приоритетом: бинарная куча, Фибоначчиева куча (последнее без доказательства)](#44-алгоритм-прима-аналогия-с-алгоритмом-дейкстры-оценка-времени-работы-для-различных-реализаций-очереди-с-приоритетом-бинарная-куча-фибоначчиева-куча-последнее-без-доказательства) + - [Алгоритм Прима](#алгоритм-прима) + - [Аналогия с алгоритмом Дейкстры](#аналогия-с-алгоритмом-дейкстры) + - [Оценка времени работы для различных реализаций очереди с приоритетом: бинарная куча, Фибоначчиева куча](#оценка-времени-работы-для-различных-реализаций-очереди-с-приоритетом-бинарная-куча-фибоначчиева-куча) + - [4.5. Алгоритм Прима. Доказательство корректности с помощью теоремы о разрезе](#45-алгоритм-прима-доказательство-корректности-с-помощью-теоремы-о-разрезе) + - [Доказательство корректности с помощью теоремы о разрезе](#доказательство-корректности-с-помощью-теоремы-о-разрезе) + - [4.6. Алгоритм Краскала. Доказательство корректности. Оценка времени работы](#46-алгоритм-краскала-доказательство-корректности-оценка-времени-работы) + - [Алгоритм Краскала](#алгоритм-краскала) + - [4.7. Система непересекающихся множеств. Эвристика потенциалов без доказательства. Эвристика сжатия пути без доказательства. Почти константное время работы](#47-система-непересекающихся-множеств-эвристика-потенциалов-без-доказательства-эвристика-сжатия-пути-без-доказательства-почти-константное-время-работы) + - [4.8. Алгоритм Борувки. Доказательство. Оценка времени работы](#48-алгоритм-борувки-доказательство-оценка-времени-работы) + - [Алгоритм Борувки](#алгоритм-борувки) + - [Оценка времени работы алгоритма Борувки](#оценка-времени-работы-алгоритма-борувки) + - [4.9. Приближение решения задачи коммивояжера с помощью минимального остовного дерева](#49-приближение-решения-задачи-коммивояжера-с-помощью-минимального-остовного-дерева) +- [5. Потоки в сетях](#5-потоки-в-сетях) + - [5.1. Определение сети. Определение потока. Физический смысл. Аналогия с законами Кирхгофа. Определение разреза. Понятия потока через разрез](#51-определение-сети-определение-потока-физический-смысл-аналогия-с-законами-кирхгофа-определение-разреза-понятия-потока-через-разрез) + - [Определение сети](#определение-сети) + - [Определение потока](#определение-потока) + - [Физический смысл. Аналогия с законами Кирхгофа](#физический-смысл-аналогия-с-законами-кирхгофа) + - [Определение разреза](#определение-разреза) + - [Понятия потока через разрез](#понятия-потока-через-разрез) + - [5.2. Доказательство факта, что поток через любой разрез одинаковый](#52-доказательство-факта-что-поток-через-любой-разрез-одинаковый) + - [5.3. Понятие остаточной сети. Понятие дополняющего пути. Необходимость отсутствия дополняющего пути для максимальности потока](#53-понятие-остаточной-сети-понятие-дополняющего-пути-необходимость-отсутствия-дополняющего-пути-для-максимальности-потока) + - [Понятие остаточной сети](#понятие-остаточной-сети) + - [Понятие дополняющего пути](#понятие-дополняющего-пути) + - [Необходимость отсутствия дополняющего пути для максимальности потока](#необходимость-отсутствия-дополняющего-пути-для-максимальности-потока) + - [5.4. Теорема Форда-Фалкерсона](#54-теорема-форда-фалкерсона) + - [5.5. Алгоритм Форда-Фалкерсона. Поиск минимального разреза. Пример целочисленной сети, в котором алгоритм работает долго](#55-алгоритм-форда-фалкерсона-поиск-минимального-разреза-пример-целочисленной-сети-в-котором-алгоритм-работает-долго) + - [Алгоритм Форда-Фалкерсона](#алгоритм-форда-фалкерсона) + - [Поиск минимального разреза](#поиск-минимального-разреза) + - [5.6. Алгоритм Эдмондса-Карпа. Доказательство, что кратчайшее расстояние в остаточной сети не уменьшается](#56-алгоритм-эдмондса-карпа-доказательство-что-кратчайшее-расстояние-в-остаточной-сети-не-уменьшается) + - [Алгоритм Эдмондса-Карпа](#алгоритм-эдмондса-карпа) + - [5.7. Общая оценка времени работы алгоритма Эдмондса-Карпа](#57-общая-оценка-времени-работы-алгоритма-эдмондса-карпа) + - [5.8. Слоистая сеть. Алгоритм Диница. Оценка времени работы без доказательства](#58-слоистая-сеть-алгоритм-диница-оценка-времени-работы-без-доказательства) + - [Слоистая сеть](#слоистая-сеть) + - [Алгоритм Диница](#алгоритм-диница) + - [Оценка времени работы без доказательства](#оценка-времени-работы-без-доказательства) +- [6. RMQ. Sparse-table, дерево отрезков. LCA. Декартово дерево по неявному ключу](#6-rmq-sparse-table-дерево-отрезков-lca-декартово-дерево-по-неявному-ключу) + - [6.1. RSQ и RMQ. Sparse-table](#61-rsq-и-rmq-sparse-table) + - [RSQ и RMQ](#rsq-и-rmq) + - [6.2. Дерево отрезков. Обработка запросов от листьев. Обработка запросов от корня](#62-дерево-отрезков-обработка-запросов-от-листьев-обработка-запросов-от-корня) + - [Дерево отрезков](#дерево-отрезков) + - [Обработка запросов от листьев. Обработка запросов от корня](#обработка-запросов-от-листьев-обработка-запросов-от-корня) + - [6.3. Изменение значения в массиве, обновление дерева отрезков. Множественные операции](#63-изменение-значения-в-массиве-обновление-дерева-отрезков-множественные-операции) + - [Изменение значения в массиве, обновление дерева отрезков](#изменение-значения-в-массиве-обновление-дерева-отрезков) + - [Множественные операции](#множественные-операции) + - [6.4. LCA. Метод двоичного подъёма](#64-lca-метод-двоичного-подъёма) + - [LCA](#lca) + - [Метод двоичного подъёма](#метод-двоичного-подъёма) + - [6.5. Декартово дерево по неявному ключу. Интерфейс быстрого массива: Доступ к элементу в позиции i, Вставка элемента в позицию i, Удаление элемента из позиции i, Конкатенация двух массивов, разделение массива на два](#65-декартово-дерево-по-неявному-ключу-интерфейс-быстрого-массива-доступ-к-элементу-в-позиции-i-вставка-элемента-в-позицию-i-удаление-элемента-из-позиции-i-конкатенация-двух-массивов-разделение-массива-на-два) + - [Декартово дерево по неявному ключу](#декартово-дерево-по-неявному-ключу) + - [Интерфейс быстрого массива](#интерфейс-быстрого-массива) + - [Конкатенация двух массивов](#конкатенация-двух-массивов) + - [Разделение массива на два](#разделение-массива-на-два) + +## 1. Обходы графа + +### 1.1. Ориентированный граф, псевдограф. Неориентированный граф, псевдограф. Связность в неориентированном графе, компоненты связности. Слабая и сильная связность в ориентированном графе. Компоненты слабой, сильной связности + +#### Ориентированный граф + +**Ориентированным графом** (англ. *directed graph*) $G$ называется пара $G = (V, E)$, где $V$ — множество вершин (англ. vertices), а $E \subset V \times V$ — множество рёбер. + +**Псевдограф (*мультиорграф*)** — это ориентированный граф, в котором разрешены кратные дуги, то есть дуги, имеющие те же начальные и конечные вершины. + +**Псевдографом** $G$ называется упорядоченная пара $G := (V, A)$, в которой $V$ — множество вершин, $A$ — мультимножество упорядоченных пар вершин. Элементы этого множества называются дугами. + +Ориентированный граф называется **слабо связным**, если соответствующий неориентированный граф является связным; + +Ориентированный граф называется **сильно связным**, если всякая вершина $v$ достижима из любой другой вершины $v$; + +**Компонентой сильной связности** ориентированного графа называется максимальный по включению сильно связный подграф. Другими словами, это подграф, любые две вершины которого принадлежат какому-либо циклу, и содержащий все такие циклы для своих вершин. + +**Компонентой слабой связности** ориентированного графа называют его максимальный слабо связный подграф. + +#### Неориентированный граф + +**Неориентированным графом** (англ. *undirected graph*) $G$ называется пара $G = (V, E)$, где $V$ — множество вершин, а $E \subset {{v,u} : v,u \in V}$ — множество рёбер. + +Формально, **псевдографом** (***мультифграфом***) $G$ называется упорядоченная пара $G := (V, E)$, в которой $V$ — множество вершин, $E$ — мультимножество неупорядоченных пар вершин. Элементы этого множества называются рёбрами. + +Неориентированный граф называется **связным**, если все его вершины достижимы из некоторой вершины (эквивалентно, из любой его вершины). + +**Компонентой связности** неориентированного графа называется максимальный по включению связный подграф + +### 1.2. Обход в глубину. Цвета вершин. Времена входа и выхода. Лемма о белых путях (с доказательством) + +#### Обход в глубину + +**Обход в глубину** (поиск в глубину, англ. *Depth-First Search*, *DFS*) — один из основных методов обхода графа, часто используемый для проверки связности, поиска цикла и компонент сильной связности и для топологической сортировки. + +```c++ +#include + +std::vector> graph; // граф +int vertex_count; // число вершин + +std::vector color; // цвет вершины (0, 1, или 2) + +std::vector time_in, time_out; // "времена" захода и выхода из вершины +int dfs_timer = 0; // "таймер" для определения времён + +void dfs (int vertex) { + time_in[vertex] = dfs_timer++; + color[vertex] = 1; + for (int u : graph[vertex]) + if (color[u] == 0) + dfs(u); + color[v] = 2; + time_out[v] = dfs_timer++; +} +``` + +#### Лемма о белых путях + +**Лемма**: Не существует такого момента выполнения поиска в глубину, в который бы существовало ребро из черной вершины в белую. + +**Доказательство**: Пусть в процессе выполнения процедуры DFS нашлось ребро из черной вершины $v$ в белую вершину u. Рассмотрим момент времени, когда мы запустили DFS(v). В этот момент вершина $v$ была перекрашена из белого в серый, а вершина u была белая. Далее в ходе выполнения алгоритма будет запущен DFS(u), поскольку обход в глубину обязан посетить все белые вершины, в которые есть ребро из v. По алгоритму вершина $v$ будет покрашена в черный цвет тогда, когда завершится обход всех вершин, достижимых из нее по одному ребру, кроме тех, что были рассмотрены раньше нее. Таким образом, вершина $v$ может стать черной только тогда, когда DFS выйдет из вершины u, и она будет покрашена в черный цвет. Получаем противоречие. + +### 1.3. Проверка связности неориентированного графа. Поиск цикла в неориентированном и ориентированном графе. Топологическая сортировка + +#### Проверка связности неориентированного графа + +Небольшая модификация алгоритма обхода в глубину, в которой будем возвращать количество посещенных вершин. Запустим такой **DFS** от некоторой вершины графа $G$, если его результат равен $|V|$, то мы побывали во всех вершинах графа, а следовательно он связен, иначе какие-то вершины остались не посещёнными. Работает алгоритм за $O(|V|+|E|)$ + +```c++ +std::vector> graph; // граф + +// visited — массив цветов вершин +int dfs(int vertex, std::vector visited): + int visitedVertices = 1 + visited[vertex] = true // помечаем вершину как пройденную + for (auto u : graph[vertex]) // проходим по смежным с u вершинам + // проверяем, не находились ли мы ранее в выбранной вершине + if (!visited[v]) + visitedVertices += dfs(v, visited) + return visitedVertices +``` + +#### Поиск цикла в неориентированном и ориентированном графе + +*Условие:* + +Пусть дан ориентированный или неориентированный граф без петель и кратных рёбер. Требуется проверить, является ли он ациклическим, а если не является, то найти любой цикл. + +*Решение:* + +Произведём серию поисков в глубину в графе. Т.е. из каждой вершины, в которую мы ещё ни разу не приходили, запустим поиск в глубину, который при входе в вершину будет красить её в серый цвет, а при выходе - в чёрный. И если поиск в глубину пытается пойти в серую вершину, то это означает, что мы нашли цикл (если граф неориентированный, то случаи, когда поиск в глубину из какой-то вершины пытается пойти в предка, не считаются). + +```c++ +#include +#include + +int n; +std::vector> graph; +std::vector color; +std::vector parent; +int cycle_start, cycle_end; + +bool dfs(int vertex) { + color[vertex] = 1; + for (size_t i = 0; i < graph[vertex].size(); ++i) { + int to = graph[vertex][i]; + if (color[to] == 0) { + parent[to] = vertex; + if (dfs(to)) return true; + } + else if (color[to] == 1) { + cycle_end = vertex; + cycle_start = to; + return true; + } + } + color[vertex] = 2; + return false; +} + +int main() { + // чтение графа + + parent.assign(n, -1); + color.assign(n, 0); + cycle_start = -1; + for (int i = 0; i < n; ++i) + if (dfs(i)) + break; + + if (cycle_st == -1) + std::cout << "Acyclic" << "\n"; + else { + std::cout << "Cyclic" << "\n"; + std::vector cycle; + cycle.push_back(cycle_start); + for (int vertex = cycle_end; vertex != cycle_start; vertex = parent[vertex]) + cycle.push_back(vertex); + cycle.push_back(cycle_start); + reverse(cycle.begin(), cycle.end()); + for (size_t i = 0; i < cycle.size(); ++i) + std::cout << cycle[i]+1 << " "; + } +} +``` + +#### Топологическая сортировка + +**Топологическая сортировка** - упорядочивание вершин ациклического ориентированного графа согласно частичному порядку, заданному ребрами орграфа на множестве его вершин. + +*Условие:* + +Дан ориентированный граф с $n$ вершинами и $m$ рёбрами. Требуется перенумеровать его вершины таким образом, чтобы каждое ребро вело из вершины с меньшим номером в вершину с большим. Иными словами, требуется найти перестановку вершин (топологический порядок), соответствующую порядку, задаваемому всеми рёбрами графа. + +Топологическая сортировка может быть не единственной (например, если граф — пустой; или если есть три такие вершины $a$, $b$, $c$, что из $a$ есть пути в $b$ и в $c$, но ни из $b$ в $c$, ни из $c$ в $b$ добраться нельзя). + +Топологической сортировки может не существовать вовсе — если граф содержит циклы (поскольку при этом возникает противоречие: есть путь и из одной вершины в другую, и наоборот). + +*Решение:* + +```c++ +#include + +int n; // число вершин +std::vector> graph; // граф +std::vector visited; +std::vector topological_sort; + +void dfs (int vertex) { + visited[vertex] = true; + for (int u : graph[vertex]) + if (!visited[u] == 0) + dfs(u); + topological_sort.push_back(vertex); +} + +void topological_sort() { + visited.assign(n, false); + answer.clear(); + for (int i = 0; i < n; ++i) + if (!visited[i]) + dfs(i); + reverse(answer.begin(), answer.end()); +} +``` + +### 1.4. Нахождение компонент сильной связности. Алгоритм Косарайю с доказательством корректности(концепция). Алгоритм Тарьяна без доказательства корректности + +#### Нахождение компонент сильной связности + +**Компонента сильной связанности** (англ. *strongly connected component*) - такое (максимальное по включению) подмножество вершин $C$, что любые две вершины этого подмножества достижимы друг из друга, т.е. для любых $u$, $v \in C: u \rightarrow v, v \rightarrow u$, где символом $\rightarrow$ здесь и далее мы будем обозначать достижимость, т.е. существование пути из первой вершины во вторую. + +*Условие:* + +Дан ориентированный граф $G$, множество вершин которого $V$ и множество рёбер — $E$. Петли и кратные рёбра допускаются. Обозначим через $n$ количество вершин графа, через $m$ — количество рёбер. + +*Решение:* + +Понятно, что компоненты сильной связности для данного графа не пересекаются, т.е. фактически это разбиение всех вершин графа. Отсюда логично определение конденсации G как графа, получаемого из данного графа сжатием каждой компоненты сильной связности в одну вершину. Каждой вершине графа конденсации соответствует компонента сильной связности графа G, а ориентированное ребро между двумя вершинами $C_i$ и $C_j$ графа конденсации проводится, если найдётся пара вершин u из $C_i$, из $C_j$, между которыми существовало ребро в исходном графе, т.е. $(u, v)$ из $E$. + +Важнейшим свойством графа конденсации является то, что он ацикличен. Действительно, предположим, что $C \rightarrow C'$, докажем, что $C' \mapsto C$. Из определения конденсации получаем, что найдутся две вершины $u$ из $C$ и $v$ из $C'$, что $u \rightarrow v$. Доказывать будем от противного, т.е. предположим, что $C' \rightarrow C$, тогда найдутся две вершины $u'$ из $C$ и $v'$ из $C'$, что $v' \rightarrow u'$. Но т.к. $u$ и $u'$ находятся в одной компоненте сильной связности, то между ними есть путь; аналогично для $v$ и $v'$. В итоге, объединяя пути, получаем, что $v \rightarrow u$, и одновременно $u \rightarrow v$. Следовательно, $u$ и $v$ должны принадлежать одной компоненте сильной связности, т.е. получили противоречие, что и требовалось доказать + +#### Алгоритм Косарайю с доказательством корректности + +**Алгоритм Косарайю** предназначен для поиска компонент сильной связности в ориентированном графе и состоит из трёх шагов: + +1. Выполнить поиск в глубину (DFS), пока не будут «помечены» все вершины. Вершина считается «помеченной», когда ей присвоено время выхода из рекурсии (см. основные понятия). +2. Инвертировать исходный граф +3. Выполнить DFS в порядке убывания пометок вершин. + +Полученные деревья каждого такта DFS последнего шага являются компонентами сильной связности + +*Условие:* + +Вершины $u$ и $v$ сильно связаны $\Leftrightarrow$ после выполнения алгоритма они принадлежат одному дереву такта DFS. + +*Доказательство корректности алгоритма:* + +Если вершины u и $v$ были сильно связаны в графе $G$, на третьем этапе будет найден путь из одной вершины в другую (по Лемме (Инвертирование рёбер цикла не влияет на его цикличность)), так как на первом шаге был найден путь $u \rightarrow v$, а на третьем – путь $v \rightarrow u$. Это означает, что по окончании алгоритма обе вершины лежат в одном дереве. + +Вершины $u$ и $v$ лежат в одном и том же дереве поиска в глубину на третьем шаге алгоритма. Значит, они обе достижимы из корня $r$ этого дерева. + +Вершина $r$ была рассмотрена на 3 шаге раньше всех, значит время выхода из неё на 1 шаге больше, чем время выхода из вершин $u$ и $v$. Из этого мы получаем 2 случая: + +Обе эти вершины были достижимы из $r$ в исходном графе. Это означает сильную связность вершин $u$ и $r$ и сильную связность вершин $v$ и $r$. Склеивая пути мы получаем связность вершин $u$ и $v$ + +Хотя бы одна вершина не достижима из $r$ в исходном графе, например $v$. Значит и $r$ была не достижима из $v$ в исходном графе, так как время выхода из $r$ — больше (если бы она была достижима, то время выхода из $v$ было бы больше, чем из $r$). Значит между этими вершинами нет пути (ни в исходном, ни в инвертированном графах), но последнего быть не может, потому что по условию $v$ достижима из $r$ на 3 шаге (в инвертированном графе) + +Значит, из случая 1 и не существования случая 2 получаем, что вершины $u$ и $v$ сильно связаны в обоих графах + +#### Алгоритм Тарьяна без доказательства корректности + +Основой для алгоритма является структура данных "Система непересекающихся множеств", которая и была изобретена Тарьяном (*Tarjan*). + +Алгоритм фактически представляет собой обход в глубину из корня дерева, в процессе которого постепенно находятся ответы на запросы. А именно, ответ на запрос $(v, u)$ находится, когда обход в глубину находится в вершине $u$, а вершина $v$ уже была посещена, или наоборот. + +Итак, пусть обход в глубину находится в вершине $v$ (и уже были выполнены переходы в её сыновей), и оказалось, что для какого-то запроса $(v, u)$ вершина u уже была посещена обходом в глубину. Научимся тогда находить $LCA$ этих двух вершин. + +Заметим, что $LCA(v, u)$ является либо самой вершиной $v$, либо одним из её предков. Получается, нам надо найти самую нижнюю вершину среди предков $v$ (включая её саму), для которой вершина u является потомком. Заметим, что при фиксированном $v$ по такому признаку (т.е. какой наименьший предок $v$ является и предком какой-то вершины) вершины дерева дерева распадаются на совокупность непересекающихся классов. Для каждого предка $p \neq v$ вершины $v$ её класс содержит саму эту вершину, а также все поддеревья с корнями в тех её сыновьях, которые лежат "слева" от пути до $v$ (т.е. которые были обработаны ранее, чем была достигнута $v$). + +Нам надо научиться эффективно поддерживать все эти классы, для чего мы и применим структуру данных "Система непересекающихся множеств". Каждому классу будет соответствовать в этой структуре множество, причём для представителя этого множества мы определим величину **ANCESTOR** — ту вершину $p$, которая и образует этот класс. + +Рассмотрим подробно реализацию обхода в глубину. Пусть мы стоим в некоторой вершине $v$. Поместим её в отдельный класс в структуре непересекающихся множеств, **ANCESTOR**[v] = $v$. Как обычно в обходе в глубину, перебираем все исходящие рёбра \$(v, $to$ )\$. Для каждого такого $to$ мы сначала должны вызвать обход в глубину из этой вершины, а потом добавить эту вершину со всем её поддеревом в класс вершины $v$. Это реализуется операцией **Union** структуры данных "система непересекающихся множеств", с последующей установкой **ANCESTOR** = $v$ для представителя множества (т.к. после объединения представитель класса мог измениться). Наконец, после обработки всех рёбер мы перебираем все запросы вида $(v, u)$, и если u была помечена как посещённая обходом в глубину, то ответом на этот запрос будет вершина $LCA(v,u)$ = **ANCESTOR**[FindSet(u)]. Нетрудно заметить, что для каждого запроса это условие (что одна вершина запроса является текущей, а другая была посещена ранее) выполнится ровно один раз. + +Оценим асимптотику. Она складывается из нескольких частей. Во-первых, это асимптотика обхода в глубину, которая в данном случае составляет $O(n)$. Во-вторых, это операции по объединению множеств, которые в сумме для всех разумных n затрачивают $O(n)$ операций. В-третьих, это для каждого запроса проверка условия (два раза на запрос) и определение результата (один раз на запрос), каждое, опять же, для всех разумных n выполняется за $O(1)$. Итоговая асимптотика получается $O(n + m)$, что означает для достаточно больших $m$ $(n = O(m))$ ответ за $O(1)$ на один запрос + +### 1.5. Компоненты реберной двусвязности. Мосты. Поиск мостов + +#### Компоненты реберной двусвязности + +**Компонентами рёберной двусвязности** (англ. *costal doubly-linked components*) графа называют его подграфы, множества вершин которых - классы эквивалентности рёберной двусвязности, а множества рёбер - множества ребер из соответствующих классов эквивалентности. + +#### Мосты + +**Мостом** называется такое ребро, удаление которого делает граф несвязным (или, точнее, увеличивает число компонент связности). + +#### Поиск мостов + +*Алгоритм:* + +Запустим обход в глубину из произвольной вершины графа; обозначим её через **root**. Заметим следующий факт (который несложно доказать): + +Пусть мы находимся в обходе в глубину, просматривая сейчас все рёбра из вершины $v$. Тогда, если текущее ребро $(v,to)$ таково, что из вершины $to$ и из любого её потомка в дереве обхода в глубину нет обратного ребра в вершину $v$ или какого-либо её предка, то это ребро является мостом. В противном случае оно мостом не является. (В самом деле, мы этим условием проверяем, нет ли другого пути из $v$ в $to$ , кроме как спуск по ребру $(v,to)$ дерева обхода в глубину.) + +Теперь осталось научиться проверять этот факт для каждой вершины эффективно. Для этого воспользуемся "временами входа в вершину", вычисляемыми алгоритмом поиска в глубину. + +Итак, пусть $tin[v]$ — это время захода поиска в глубину в вершину $v$. Теперь введём массив $fup[v]$, который и позволит нам отвечать на вышеописанные запросы. Время $fup[v]$ равно минимуму из времени захода в саму вершину $tin[v]$, времён захода в каждую вершину $p$, являющуюся концом некоторого обратного ребра $(v, p)$, а также из всех значений $fup[to]$ для каждой вершины $to$ , являющейся непосредственным сыном $v$ в дереве поиска: + +$fup[v] = \min(tin[v], tin[p], fup(to))$ $(v, p)$ - back edge, $(v, to)$ - tree edge (здесь "back edge" — обратное ребро, "tree edge" — ребро дерева) + +Тогда, из вершины $v$ или её потомка есть обратное ребро в её предка тогда и только тогда, когда найдётся такой сын $to$ , что $fup[to] \leq tin[v]$. (Если $fup[to] = tin[v]$, то это означает, что найдётся обратное ребро, приходящее точно в $v$; если же $fup[to] < tin[v]$, то это означает наличие обратного ребра в какого-либо предка вершины $v$.) + +Таким образом, если для текущего ребра $(v,to)$ (принадлежащего дереву поиска) выполняется $fup[to] > tin[v]$, то это ребро является мостом; в противном случае оно мостом не является. + +### 1.6. Компоненты вершинной двусвязности. Точки сочленения. Поиск точек сочленения + +#### Компоненты вершинной двусвязности + +**Компонентами вершинной двусвязности** графа, называют его подграфы, множества ребер которых — классы эквивалентности вершинной двусвязности, а множества вершин — множества всевозможных концов ребер из соответствующих классов. + +#### Точки сочленения + +**Точкой сочленения** называется вершина, при удалении которой связный неориентированный граф становится несвязным. + +#### Поиск точек сочленения + +Алгоритм: + +Запустим обход в глубину из произвольной вершины графа; обозначим её через $root$. Заметим следующий факт (который несложно доказать): + +Пусть мы находимся в обходе в глубину, просматривая сейчас все рёбра из вершины $v \neq root$. Тогда, если текущее ребро $(v, to)$ таково, что из вершины $to$ и из любого её потомка в дереве обхода в глубину нет обратного ребра в какого-либо предка вершины v, то вершина $v$ является точкой сочленения. В противном случае, т.е. если обход в глубину просмотрел все рёбра из вершины $v$, и не нашёл удовлетворяющего вышеописанным условиям ребра, то вершина $v$ не является точкой сочленения. (В самом деле, мы этим условием проверяем, нет ли другого пути из $v$ в $to$ ) + +Рассмотрим теперь оставшийся случай: $v = root$. Тогда эта вершина является точкой сочленения тогда и только тогда, когда эта вершина имеет более одного сына в дереве обхода в глубину. (В самом деле, это означает, что, пройдя из $root$ по произвольному ребру, мы не смогли обойти весь граф, откуда сразу следует, что $root$ — точка сочленения). + +Теперь осталось научиться проверять этот факт для каждой вершины эффективно. Для этого воспользуемся "временами входа в вершину", вычисляемыми алгоритмом поиска в глубину. + +Итак, пусть $tin[v]$ — это время захода поиска в глубину в вершину v. Теперь введём массив $fup[v]$, который и позволит нам отвечать на вышеописанные запросы. Время $fup[v]$ равно минимуму из времени захода в саму вершину $tin[v]$, времён захода в каждую вершину p, являющуюся концом некоторого обратного ребра $(v, p)$, а также из всех значений $fup[to]$ для каждой вершины $to$ , являющейся непосредственным сыном $v$ в дереве поиска: + +$fup[v] = \min(tin[v], tin[p], fup(to))$ $(v, p)$ - back edge, $(v, to)$ - tree edge (здесь "back edge" — обратное ребро, "tree edge" — ребро дерева) + +Тогда, из вершины $v$ или её потомка есть обратное ребро в её предка тогда и только тогда, когда найдётся такой сын $to$ , что $fup[to] < tin[v]$. + +Таким образом, если для текущего ребра $(v,to)$ (принадлежащего дереву поиска) выполняется $fup[to] \geq tin[v]$, то вершина $v$ является точкой сочленения. Для начальной вершины $v = root$ критерий другой: для этой вершины надо посчитать число непосредственных сыновей в дереве обхода в глубину. + +### 1.7. Волновой алгоритм. Обход в ширину (применение очереди в волновом алгоритме) + +#### Волновой алгоритм + +**Алгоритм волновой трассировки** (*волновой алгоритм*) — алгоритм поиска пути, алгоритм поиска кратчайшего пути на планарном графе. Принадлежит к алгоритмам, основанным на методах поиска в ширину. + +Алгоритм предназначен для поиска кратчайшего пути от стартовой ячейки к конечной ячейке, если это возможно, либо, при отсутствии пути, выдать сообщение о непроходимости. + +Работа алгоритма включает в себя три этапа: инициализацию, распространение волны и восстановление пути. + +Инициализация + +```c +Пометить стартовую ячейку +d := 0 +``` + +Распространение волны + +```c +ЦИКЛ + ДЛЯ каждой ячейки loc, помеченной числом d + пометить все соседние свободные непомеченные ячейки числом d + 1 + КЦ + d := d + 1 +ПОКА (финишная ячейка не помечена) И (есть возможность распространения волны) +``` + +Восстановление пути + +```c +ЕСЛИ финишная ячейка помечена +ТО + перейти в финишную ячейку + ЦИКЛ + выбрать среди соседних ячейку, помеченную числом на 1 меньше числа в текущей ячейке + перейти в выбранную ячейку и добавить её к пути + ПОКА текущая ячейка — не стартовая + ВОЗВРАТ путь найден +ИНАЧЕ + ВОЗВРАТ путь не найден +``` + +#### Обход в ширину (применение очереди в волновом алгоритме) + +**Поиск в ширину** (англ. *breadth-first search*) — один из основных алгоритмов на графах, позволяющий находить все кратчайшие пути от заданной вершины и решать многие другие задачи. + +Описание алгоритма + +На вход алгоритма подаётся невзвешенный граф и номер стартовой вершины $s$. Граф может быть как ориентированным, так и неориентированным — для алгоритма это не важно. + +Основную идею алгоритма можно понимать как процесс «поджигания» графа: на нулевом шаге мы поджигаем вершину $s$, а на каждом следующем шаге огонь с каждой уже горящей вершины перекидывается на всех её соседей, в конечном счете поджигая весь граф. + +Если моделировать этот процесс, то за каждую итерацию алгоритма будет происходить расширение «кольца огня» в ширину на единицу. Номер шага, на котором вершина $v$ начинает гореть, в точности равен длине её минимального пути из вершины s. + +```c++ +std::vector graph; + +void bfs(int s) { + std::queue queue; + queue.push(s); + + std::vector depth(n, -1), parent(n); + depth[s] = 0; + + while (!queue.empty()) { + int vertex = queue.front(); + queue.pop(); + for (int u : g[vertex]) { + if (depth[u] == -1) { + queue.push(u); + depth[u] = depth[vertex] + 1; + parent[u] = vertex; + } + } + } +} +``` + +Теперь, чтобы восстановить кратчайший путь до какой-то вершины v, это можно сделать через массив p: + +```c++ +while (v != s) { + std::cout << v << endl; + v = p[v]; +} +``` + +Обратим внимание, что путь выведется в обратном порядке. + +### 1.8. Критерий существования Эйлерова пути и цикла в ориентированном и неориентированном графе. Поиск эйлерова пути и цикла + +#### Критерий существования Эйлерова пути и цикла в неориентированном графе + +Согласно теореме, доказанной Эйлером, эйлеров цикл существует тогда и только тогда, когда + +1. Граф связный или будет являться связным, если удалить из него все изолированные вершины. +2. В нём отсутствуют вершины нечётной степени. + +Эйлеров путь в графе существует тогда и только тогда, когда + +1. Граф связный +2. Содержит не более двух вершин нечётной степени. Ввиду леммы о рукопожатиях, число вершин с нечётной степенью должно быть чётным. А значит эйлеров путь существует только тогда, когда это число равно нулю или двум. Причём, когда оно равно нулю, эйлеров путь вырождается в эйлеров цикл. + +#### Критерий существования Эйлерова пути и цикла в ориентированном графе + +В ориентированном графе $G = (V, E)$ существует эйлеров цикл тогда и только тогда, когда: + +1. Входная степень любой вершины равна ее выходной степени. +2. Все компоненты слабой связности кроме, может быть одной, не содержат ребер. + +В ориентированном графе $G = (V, E)$ существует эйлеров путь если: + +1. Входная степень любой вершины равна ее выходной степени, кроме двух вершин графа, для одной из которых $(deg+) − (deg−) = 1$, а для другой $(deg+) − (deg−) = −1$. +2. Все компоненты слабой связности кроме, может быть одной, не содержат ребер. + +#### Поиск эйлерова пути и цикла + +**Поиск эйлерова пути в графе:** + +Можно всегда свести задачу поиска эйлерова пути к задаче поиска эйлерова цикла. Действительно, предположим, что эйлерова цикла не существует, а эйлеров путь существует. Тогда в графе будет ровно 2 вершины нечётной степени. Соединим эти вершины ребром, и получим граф, в котором все вершины чётной степени, и эйлеров цикл в нём существует. Найдём в этом графе эйлеров цикл (алгоритмом, описанным ниже), а затем удалим из ответа несуществующее ребро. + +**Поиск эйлерова цикла в графе:** + +Будем рассматривать самый общий случай — случай ориентированного мультиграфа, возможно, с петлями. Также мы предполагаем, что эйлеров цикл в графе существует (и состоит хотя бы из одной вершины). Для поиска эйлерова цикла воспользуемся тем, что эйлеров цикл — это объединение всех простых циклов графа. Следовательно, наша задача — эффективно найти все циклы и эффективно объединить их в один. + +Реализовать это можно, например, так, рекурсивно: + +```c +procedure find_all_cycles (v) +var массив cycles +1. пока есть цикл, проходящий через v, находим его добавляем все вершины найденного цикла в массив cycles (сохраняя порядок обхода) удаляем цикл из графа +2. идем по элементам массива cycles каждый элемент cycles[i] добавляем к ответу из каждого элемента рекурсивно вызываем себя: find_all_cycles(cycles[i]) +``` + +Достаточно вызвать эту процедуру из любой вершины графа, и она найдёт все циклы в графе, удалит их из графа и объединит их в один эйлеров цикл. +Для поиска цикла на шаге 1 используем поиск в глубину. +Сложность полученного алгоритма — $O(|E|)$, то есть линейная относительно количества рёбер в данном графе + +## 2. Планарность графа + +### 2.1. Формула Эйлера. Теорема Портнягина-Куратовского. Теорема Вагнера + +#### Формула Эйлера + +Для произвольного плоского связного графа $G$ с $V$ вершинами, $E$ ребрами и $F$ гранями справедливо следующее соотношение: $V − E + F = 2$. + +#### Теорема Портнягина-Куратовского + +**Теорема Понтрягина — Куратовского**, или **теорема Куратовского**, — теорема в теории графов, дающая необходимое и достаточное условие планарности графа. Имеет эквивалентную формулировку на языке миноров, так называемою теорему Вагнера. + +Теорема утверждает, что графы $K_5$ (полный граф на 5 вершинах) и $K_{3,3}$ (полный двудольный граф имеющий по 3 вершины в каждой доле) являются единственными минимальными непланарными графами. + +#### Теорема Вагнера + +**Теорема Вагнера** — характеризация планарных графов тесно связанная с теоремой Понтрягина — Куратовского. + +Теорема утверждает, что конечный граф является планарным тогда и только тогда, когда его миноры не включают ни $K_5$ (полный граф с пятью вершинами), ни $K_{3,3}$ (коммунальный граф, полный двудольный граф с тремя вершинами в каждой доле). + +### 2.2. Гамма алгоритм. Контактная вершина + +#### Гамма алгоритм + +Чтобы проверить планарность графа и произвести его плоскую укладку, удобно пользоваться гамма-алгоритмом. +Определения: + +1. Сегмент графа $Г$ относительно подграфа $H$ — компонента связности графа $G \setminus H$. +2. **Допустимая грань сегмента** — грань графа $H$, содержащая все контактные вершины. +3. **Контактная вершина** сегмента $S$ графа $G$ подграфа $H$ — любая вершина в $S$ и в $H$. +4. **Гамма-цепь сегмента** — любая цепь без повторов вершин, содержащих ровно две контактные вершины — начало и конец. + +Алгоритм: + +1. Уложить на плоскость любой цикл $H$ графа $G$ без повторов вершин. +2. Построить все сегменты $S_1$ , $...$ , $S_k$ графа $G$ по $H$. +3. Если есть сегмент $S_i$ c одной допустимой гранью — выбрать его. +4. Если все сегменты имеют несколько дополнительных граней — выбрать любой. +5. Выбрать произвольную гамма-цепь сегмента и уложить её в допустимую грань. +6. Перейти к шагу (2), добавив гамма-цепь к графу $H$. + +### 2.3. Теорема о корректности Гамма алгоритма. Асимптотика алгоритма + +#### Теорема о корректности Гамма алгоритма + +**Теорема:** +Гамма-алгоритм корректен, то есть если $G$ — планарный граф, то результатом каждого шага гамма-алгоритма является частичная укладка $G'$. + +**Доказательство:** + +Докажем индукцией по числу шагов. + +База индукции: полученный на этапе инициализации граф $G_0$ является простым циклом, он будет присутствовать в любой укладке графа $G$. Таким образом, $G_0$ является частичной укладкой. + +Шаг индукции: пусть граф $G_{k−1}$, полученный на $k−1$-м шаге работы алгоритма, является частичной укладкой. Докажем, что граф $G_k = G_{k−1} \cup L_k$, полученный на $k$-м шаге присоединением цепи $L_k$, также является частичной укладкой. + +Заметим, что на текущем шаге нет такого сегмента $S$ относительно $G_{k−1}$, для которого бы выполнялось равенство $Γ(S) = \varnothing$, так как в противном случае существовала бы цепь этого сегмента, контактные вершины которой принадлежали бы разным граням и укладка которой была бы невозможна. Следовательно, нельзя было бы уложить $S$,что противоречит тому, что $G$ — планарный граф. Значит, мы можем рассматривать только следующие два случая: + +1. Существует сегмент $S$ для которого есть единственная вмещающая грань $Γ$, то есть $|Γ(S)| = 1$. Так как только грани $Γ$ принадлежат все контактные вершины $S$, то укладка этого сегмента в эту грань неизбежна. Это значит, что помещая любую цепь $L \subset S$, снова получим частичную укладку графа. +2. Для любого сегмента $S$ $|Γ(S)| \geq 2$. Построим граф $A(G'k−1)$, который по лемме 2 является двудольным. Рассмотрим его связную компоненту $K$, которая содержит не менее двух вершин. Граф $K$ также является двудольным. По лемме 1 для любого сегмента $S \in K$ справедливо $Γ(S)={Γ1, Γ2}$. Так как граф $K$ двудольный, то мы можем по очереди помещать сегменты $K$ в разные грани, причем конфликтующих сегментов не возникнет в силу четности всех циклов в графе. Результатом будет частичная укладка графа. + +Таким образом, на каждом шаге мы получаем частичную укладку графа, что доказывает корректность гамма-алгоритма. + +**Асимптотика алгоритма - $O(n^3)$.** + +## 3. Кратчайшие пути во взвешенном графе + +### 3.1. Алгоритм Дейкстры. Доказательство корректности(с доказательством). Оценка времени работы. Дерево кратчайших путей + +#### Алгоритм Дейкстры + +**Алгоритм Дейкстры** (англ. *Dijkstra’s algorithm*) — алгоритм на графах, изобретённый нидерландским учёным Эдсгером Дейкстрой в 1959 году. Находит кратчайшие пути от одной из вершин графа до всех остальных. Алгоритм работает только для графов без рёбер отрицательного веса. + +```c +func dijkstra(s): + for v in V + d[v] = infinity + used[v] = false + d[s] = 0 + for i in V + v = null + for j in V // найдём вершину с минимальным расстоянием + if !used[j] and (v == null or d[j] < d[v]) + v = j + if d[v] == infinity + break + used[v] = true + for e : исходящие из v рёбра // произведём релаксацию по всем рёбрам, исходящим из v + if d[v] + e.len < d[e.to] + d[e.to] = d[v] + e.len +``` + +#### Доказательство корректности + +Пусть $G=(V,E)$ — ориентированный взвешенный граф, вес рёбер которого неотрицателен, s — стартовая вершина. Тогда после выполнения алгоритма Дейкстры $d(u)=\rho(s, u)$ для всех $u$, где $\rho (s,u)$ — длина кратчайшего пути из вершины $s$ в вершину $u$. + +Докажем по индукции, что в момент посещения любой вершины $u$, $d(u)= \rho (s,u)$. + +На первом шаге выбирается $s$, для неё выполнено: $d(s) = \rho (s,s) = 0$. Пусть для $n$ первых шагов алгоритм сработал верно и на $n+1$ шагу выбрана вершина $u$. Докажем, что в этот момент $d(u)= \rho (s,u)$. Для начала отметим, что для любой вершины $v$, всегда выполняется $d(v) \geq \rho (s,v)$ (алгоритм не может найти путь короче, чем кратчайший из всех существующих). Пусть $P$ — кратчайший путь из $s$ в $u$, $v$ — первая непосещённая вершина на $P$, $z$ — предшествующая ей (следовательно, посещённая). Поскольку путь $P$ кратчайший, его часть, ведущая из $s$ через $z$ в $v$, тоже кратчайшая, следовательно $\rho(s,v)= \rho(s,z)+w(zv)$. По предположению индукции, в момент посещения вершины $z$ выполнялось $d(z)= \rho (s,z)$, следовательно, вершина $v$ тогда получила метку не больше чем $d(z) + w(zv) = \rho (s,z) + w(zv) = \rho(s,v)$, следовательно, $d(v)=\rho(s,v)$. С другой стороны, поскольку сейчас мы выбрали вершину $u$, её метка минимальна среди непосещённых, то есть $d(u) \leq d(v) = \rho (s,v) \leq \rho (s,u)$, где второе неравенсто верно из-за ранее упомянутого определения вершины $v$ в качестве первой непосещённой вершины на $P$, то есть вес пути до промежуточной вершины не превосходит веса пути до конечной вершины вследствие неотрицательности весовой функции. Комбинируя это с $d(u) \geq \rho(s,u)$, имеем $d(u)=\rho(s,u)$, что и требовалось доказать. Поскольку алгоритм заканчивает работу, когда все вершины посещены, в этот момент $d(u)= \rho (s,u)$ для всех $u$. + +#### Оценка времени работы + +Если $m = n ^ 2$(граф плотный), то $O(n^2)$. +Если $m = n$(граф разреженный), то $O(m log n)$. + +#### Дерево кратчайших путей + +Решение ряда прикладных задач сводится к нахождению дерева кратчайших путей с корнем в заданной вершине $s$. Такое дерево дает кратчайшие $(s, v)$-пути от заданной вершины $s$ до любой вершины $v$ орграфа $G = (V, Е)$. Если веса всех дуг орграфа $G$ неотрицательны, то дерево кратчайших путей можно построить с помощью алгоритма Дейкстры. + +### 3.2. Потенциалы. Условие применимости алгоритма Дейкстры для измененных длин ребер. Потенциал $\pi(v) = \rho(v, t)$ + +#### Потенциалы + +Пусть дана транспортная сеть $G(V,E)$, где $V$ — множество вершин графа, а $E$ — множество рёбер. Введем в каждой вершине потенциал $\pi(v)$. Тогда потенциальный вес (то есть стоимость) ребра $(u,v)$ определяется как $w_p(u,v)=w(u,v)+\pi(u)−\pi(v)$ + +#### Условие применимости алгоритма Дейкстры для измененных длин ребер + +Можно применять, когда нет отрицательных циклов (следует из курса дискретной математики 1 курс 2 семестр) + +#### Потенциал $\pi(v) = \rho(v, t)$ + +### 3.3. Алгоритм A*. Условие монотонности на эвристику. Примеры эвристик + +#### Алгоритм A* + +**Алгоритм A*** - алгоритм поиска по первому наилучшему совпадению на графе, который находит маршрут с наименьшей стоимостью от одной вершины (начальной) к другой (целевой, конечной). + +Порядок обхода вершин определяется эвристической функцией «расстояние + стоимость» (обычно обозначаемой как $f(x)$). Эта функция — сумма двух других: функции стоимости достижения рассматриваемой вершины (x) из начальной (обычно обозначается как $g(x)$ и может быть как эвристической, так и нет), и функции эвристической оценки расстояния от рассматриваемой вершины к конечной (обозначается как $h(x)$). + +1. Функция $h(x)$ должна быть *допустимой эвристической оценкой*, то есть не должна переоценивать расстояния к целевой вершине. Например, для задачи маршрутизации $h(x)$ может представлять собой расстояние до цели по прямой линии, так как это физически наименьшее возможное расстояние между двумя точками. +2. Более сильное условие — функция $h(v)$ должна быть **монотонной**. (Эвристическая функция $h(v)$ называетя **монотонной** (или преемственной), если для любой вершины $v_1$ и ее потомка $v_2$ разность $h(v_1)$ и $h(v_2)$ не превышает фактического веса ребра $c(v_1, v_2)$ от $v_1$ до $v_2$, а эвристическая оценка целевого состояния равна нулю.) + +```c +function A*(start, goal, f) + % множество уже пройденных вершин + var closed := the empty set + % множество частных решений + var open := make_queue(f) + enqueue(open, path(start)) + while open is not empty + var p := remove_first(open) + var x := the last node of p + if x in closed + continue + if x = goal + return $\rho$ + add(closed, x) + % добавляем смежные вершины + foreach y in successors(x) + enqueue(open, add_to_path(p, y)) + return failure +``` + +#### Примеры эвристик + +- Если мы можем перемещаться в четырех направлениях, то в качестве эвристики стоит выбрать манхэттенское расстояние $h(v)=|v.x−goal.x|+|v.y−goal.y|$. +- Расстояние Чебышева применяется, когда к четырем направлениям добавляются диагонали: $h(v)=max(|v.x−goal.x|,|v.y−goal.y|)$. +- Если передвижение не ограничено сеткой, то можно использовать евклидово расстояние по прямой: $h(v)=((v.x−goal.x) ^ 2+(v.y−goal.y)^2) ^ {1/2}$. + +### 3.4. Алгоритм Форда-Беллмана. Хранение в матрице: $D_{vk}$ равно длине кратчайшего пути до вершины $v$ за ровно $k$ ребер (не более $k$ ребер). Доказательство корректности (полное). Оценка времени работы + +Количество путей длины $k$ рёбер можно найти с помощью метода динамического программирования. Пусть $d[k][u]$ — количество путей длины $k$ рёбер, заканчивающихся в вершине $u$. Тогда $d[k][u]= \Sigma v: v, u \in E(d[k−1][v])$. + +Аналогично посчитаем пути кратчайшей длины. Пусть $s$ — стартовая вершина. Тогда $d[k][u]=\min v: v, u \in E(d[k−1][v]+w(u,v))$, при этом $d[0][s]=0$, а $d[0][u]=+\infty$ + +Если существует кратчайший путь от s до t, то $\rho(s,t)=\min d[k][t](k=0..n−1)$ + +Пусть кратчайший путь состоит из $k$ ребер, тогда корректность формулы следует из динамики, приведенной ниже. + +```c++ +for k = 0 to |V|−2 // вершины нумеруются с единицы + for v∈V + for (u,v)∈E + d[k + 1][v] = min(d[k + 1][v], d[k][u] + ω(u,v)) // ω(u,v) — вес ребра uv +``` + +```c++ +bool fordBellman(s): + for v∈V + d[v] = 1 + d[s] = 0 + for i = 0 to |V|−1 + for (u,v)∈E + if d[v] > d[u] + ω(u,v) // ω(u,v) — вес ребра uv + d[v] = d[u] + ω(u,v) + for (u,v)∈E + if d[v] > d[u] + ω(u,v) + return false + return true +``` + +#### Доказательство корректности алгоритма Форда-Беллмана + +Во-первых, сразу заметим, что для недостижимых из $v$ вершин алгоритм отработает корректно: для них метка $d[]$ так и останется равной бесконечности (т.к. алгоритм Форда-Беллмана найдёт какие-то пути до всех достижимых из $s$ вершин, а релаксация во всех остальных вершинах не произойдёт ни разу). + +Докажем теперь следующее утверждение: после выполнения $i$ фаз алгоритм Форда-Беллмана корректно находит все кратчайшие пути, длина которых (по числу рёбер) не превосходит $i$. + +Иными словами, для любой вершины $a$ обозначим через $k$ число рёбер в кратчайшем пути до неё (если таких путей несколько, можно взять любой). Тогда это утверждение говорит о том, что после $k$ фаз этот кратчайший путь будет найден гарантированно. + +**Доказательство.** + +Рассмотрим произвольную вершину a, до которой существует путь из стартовой вершины v, и рассмотрим кратчайший путь до неё: $(p_0=v, p_1, ..., p_k=a)$. Перед первой фазой кратчайший путь до вершины $p_0=v$ найден корректно. Во время первой фазы ребро $(p_0,p_1)$ было просмотрено алгоритмом Форда-Беллмана, следовательно, расстояние до вершины $p_1$ было корректно посчитано после первой фазы. Повторяя эти утверждения k раз, получаем, что после $k$-й фазы расстояние до вершины $p_k=a$ посчитано корректно, что и требовалось доказать. + +Последнее, что надо заметить — это то, что любой кратчайший путь не может иметь более $n-1$ ребра. Следовательно, алгоритму достаточно произвести только $n-1$ фазу. После этого ни одна релаксация гарантированно не может завершиться улучшением расстояния до какой-то вершины. + +#### Оценка времени работы алгоритма Форда-Беллмана + +Инициализация занимает $\theta(V)$ времени, каждый из $|V|−1$ проходов требует $\theta(E)$ времени, обход по всем ребрам для проверки наличия отрицательного цикла занимает $O(E)$ времени. Значит алгоритм Беллмана-Форда работает за $O(VE)$ времени. + +### 3.5. Восстановление пути. Детектирование цикла отрицательного веса. Поиск самого цикла + +#### Восстановление пути + +```c++ +void solve() { + vector d (n, INF); + d[v] = 0; + vector p (n, -1); + for (;;) { + bool any = false; + for (int j=0; j d[e[j].a] + e[j].cost) { + d[e[j].b] = d[e[j].a] + e[j].cost; + p[e[j].b] = e[j].a; + any = true; + } + if (!any) break; + } + + if (d[t] == INF) + cout << "No path from " << v << " to " << t << "."; + else { + vector path; + for (int cur=t; cur!=-1; cur=p[cur]) + path.push_back (cur); + reverse (path.begin(), path.end()); + + cout << "Path from " << v << " to " << t << ": "; + for (size_t i=0; iKey << "," << t->Id->Key << ") "; + Ostov_Depth ((*t).Id); + } + t = (*t).Next; + } +} +``` + +### 4.2. Определение минимального остовного дерева + +**Минимальное остовное дерево** (англ. *minimum spanning tree*) графа — это его ациклический связный подграф, в который входят все его вершины, обладающий минимальным суммарным весом ребер. Заметим, что граф может содержать несколько минимальных остовных деревьев. + +### 4.3. Теорема о разрезе. Доказательство + +#### Теорема о разрезе + +**Теорема Форда — Фалкерсо́на** — теорема о максимальном потоке в графе, тесно связанная с теоремой Менгера. +Звучит так: величина максимального потока в графе путей равна величине пропускной способности его минимального разреза. + +*Доказательство:* + +**Достаточность**: любой поток между вершинами $t$ и $s$ меньше или равен величине любого сечения. Пусть дан некоторый поток и некоторое сечение. Величина данного потока складывается из величин «грузов», перевозимых по всем возможным путям из вершины $t$ в $s$. Каждый такой путь обязан иметь общее ребро с данным сечением. Так как по каждому ребру сечения суммарно нельзя перевести «груза» больше, чем его пропускная способность, поэтому сумма всех грузов меньше или равна сумме всех пропускных способностей рёбер данного сечения. Утверждение доказано. + +Отсюда следует, что любой поток меньше или равен величине минимального сечения, а значит и максимальный поток меньше или равен величине минимального сечения. + +### 4.4. Алгоритм Прима. Аналогия с алгоритмом Дейкстры. Оценка времени работы для различных реализаций очереди с приоритетом: бинарная куча, Фибоначчиева куча (последнее без доказательства) + +#### Алгоритм Прима + +**Алгоритм Прима** (англ. *Prim's algorithm*) — алгоритм поиска минимального остовного дерева (англ. minimum spanning tree, MST) во взвешенном неориентированном связном графе. + +#### Аналогия с алгоритмом Дейкстры + +Данный алгоритм очень похож на алгоритм Дейкстры. Будем последовательно строить поддерево $F$ ответа графе $G$, поддерживая приоритетную очередь $Q$ из вершин $G \setminus F$, в которой ключом для вершины $v$ является $\min(\omega(uv) (u \in V(F), (u, v) \in E(G)))$ — вес минимального ребра из вершин $F$ в вершины $G \setminus F$. Также для каждой вершины в очереди будем хранить $p(v)$ — вершину $u$, на которой достигается минимум в определении ключа. Дерево $F$ поддерживается неявно, и его ребра — это пары $(v,p(v))$, где $v \in G \setminus {r} \setminus Q$, а $r$ — корень $F$. Изначально $F$ пусто и значения ключей у всех вершин равны $+\infty$. Выберём произвольную вершину $r$ и присвоим её ключу значение 0. На каждом шаге будем извлекать минимальную вершину $v$ из приоритетной очереди и релаксировать все ребра $(v, u)$, такие что $u \in Q$, выполняя при этом операцию $\rm decreaseKey$ над очередью и обновление $p(v)$. Ребро $(v,p(v))$ при этом добавляется к ответу. + +#### Оценка времени работы для различных реализаций очереди с приоритетом: бинарная куча, Фибоначчиева куча + +Двоичная куча - $O(ElogV)$ + +(Чтобы упростить операцию $\rm decreaseKey$ можно написать кучу на основе сбалансированного бинарного дерева поиска. Тогда просто удалим вершину и добавим ее обратно уже с новым ключом. Асимптотика таких преобразований $O(logn)$. Если же делать с бинарной кучей, то вместо операции $\rm decreaseKey$, будем всегда просто добавлять вершину с новым ключом, если из кучи достали вершину с ключом, значение которого больше чем у нее уже стоит, просто игнорировать. Вершин в куче будет не больше $2 \cdot n$, следовательно, операция $\rm extractMin$ будет выполняться за $O(log(\cdot n))$, что равно $O(logn)$. Максимальное количество вершин, которое мы сможем достать, равняется количеству ребер, то есть m, поэтому общая асимптотика составит $O(mlogn)$, что хорошо только на разреженных графах.) + +Фибоначчиева куча - $O(VlogV+E)$ + +### 4.5. Алгоритм Прима. Доказательство корректности с помощью теоремы о разрезе + +Приведем еще раз теорему о разрезе: +Для любого разбиения множества вершин $V = A \cup B$, существует минимальный остов, содержащий $e$ – минимальное по весу ребро, проходящее через разрез $\langle A, B \rangle$ + +Доказательство. Возьмём минимальный остов без $e$. Добавим $e$, получится цикл. Два ребра этого цикла проходят через разрез $\langle A, B \rangle$. Старое неменьше $e$, удалим его. Если же в процессе построения $\rm min$ остова уже известно подмножество рёбер будущего $\rm min$ остова, эти рёбра задают компоненты связности. Мы можем сжать компоненты в вершины и для конденсации применить лемму о разрезе. + +Следствие $X$ – подмножество рёбер некого минимального остова. Зафиксируем разрез $V = A \cup B$ такой, что все рёбра $X$ не пересекают разрез. Тогда $\exists$ минимальный остов, содержащий $X \cup \{e\}$, где $e$ – минимальное по весу ребро, проходящее через разрез $\langle A, B \rangle$. + +#### Доказательство корректности с помощью теоремы о разрезе + +Разрез – текущее множество $A$ и дополнение $V \setminus A$. + +### 4.6. Алгоритм Краскала. Доказательство корректности. Оценка времени работы + +#### Алгоритм Краскала + +Будем последовательно строить подграф $F$ графа $G$ ("растущий лес"), пытаясь на каждом шаге достроить $F$ до некоторого $MST$. Начнем с того, что включим в $F$ все вершины графа $G$. Теперь будем обходить множество $E(G)$ в порядке неубывания весов ребер. Если очередное ребро $e$ соединяет вершины одной компоненты связности $F$, то добавление его в остов приведет к возникновению цикла в этой компоненте связности. В таком случае, очевидно, $e$ не может быть включено в $F$. Иначе $e$ соединяет разные компоненты связности $F$, тогда существует $\langle S, T \rangle$ разрез такой, что одна из компонент связности составляет одну его часть, а оставшаяся часть графа — вторую. Тогда $e$ — минимальное ребро, пересекающее этот разрез. Значит, из леммы о безопасном ребре следует, что $e$ является безопасным, поэтому добавим это ребро в $F$. На последнем шаге ребро соединит две оставшиеся компоненты связности, полученный подграф будет минимальным остовным деревом графа $G$. Для проверки возможности добавления ребра используется система непересекающихся множеств. + +**Доказательство корректности:** + +Обратимся к теореме о разрезе из Пункта 4.5 +Добавляем ребро $(a, b)$. Разрез – любой такой, что $(a, b)$ через него проходит. + +**Оценка времени работы** - $O(ElogE)$. + +### 4.7. Система непересекающихся множеств. Эвристика потенциалов без доказательства. Эвристика сжатия пути без доказательства. Почти константное время работы + +### 4.8. Алгоритм Борувки. Доказательство. Оценка времени работы + +#### Алгоритм Борувки + +Алгоритм состоит из нескольких шагов: + +1. Изначально каждая вершина графа $G$ — тривиальное дерево, а ребра не принадлежат никакому дереву. +2. Для каждого дерева $T$ найдем минимальное инцидентное ему ребро. Добавим все такие ребра. +3. Повторяем шаг 2 пока в графе не останется только одно дерево $T$. + +Данный алгоритм может работать неправильно, если в графе есть ребра равные по весу. Например, полный граф из трех вершин, вес каждого ребра равен один. В $T$ могут быть добавлены все три ребра. Избежать эту проблему можно, например, выбирая в первом пункте среди ребер, равных по весу, ребро с наименьшим номером. + +**Доказательство:** + +Обратимся к теореме о разрезе из Пункта 4.5 +Рёбра, которые хочет добавить Борувка, разобьём на компоненты связности. Каждая компонента – дерево, рёбра компоненты будем добавлять по одному от корня к листьям. Добавляя +очередное ребро ведущее в лист $v$, используем разрез $\langle \{v\}, V \setminus \{v\} \rangle$ и лемму о разрезе. + +##### Оценка времени работы алгоритма Борувки + +На $i$-ой итерации внешнего цикла каждая компонента состоит как минимум из двух компонент из $(i−1)$-й итерации. Значит, на каждой итерации число компонент уменьшается как минимум в 2 раза. Тогда внешний цикл повторяется $O(logV)$ раз, так как количество компонент изначально равно количеству вершин. Что же касается внутреннего цикла, то он выполняется за $O(E)$, где $E$ — количество рёбер в исходном графе. Следовательно конечное время работы алгоритма $O(ElogV)$. + +### 4.9. Приближение решения задачи коммивояжера с помощью минимального остовного дерева + +**Алгоритм Кристофидеса** или **алгоритм Кристофидеса-Сердюкова** — это алгоритм поиска приближённых решений задачи коммивояжёра для случаев, когда расстояния образуют метрическое пространство (симметричны и удовлетворяют неравенству треугольника). + +Алгоритм является аgпроксимационным алгоритмом, который гарантирует, что решения находятся в пределах 3/2 от длины оптимального решения. Алгоритм назван именем Никоса Кристофидеса и Анатолия Ивановича Сердюкова, которые независимо друг от друга нашли его в 1976, и он обладает лучшим аппроксимационным коэффициентом, который был доказан для задачи коммивояжёра на метрических пространствах общего вида, хотя известны лучшие приближения для некоторых специальных случаев. + +Пусть $G=(V,w)$ будет представителем задачи коммивояжёра. То есть $G$ является полным графом на множестве вершин $V$, а функция $w$ назначает неотрицательные вещественные веса каждому ребру графа $G$. +Алгоритм можно описать на псевдокоде следующим образом: + +1. Создаём минимальное остовное дерев $T$ графа $G$. +2. Пусть $O$ будет набором вершин с нечётными степенями в $T$. Согласно лемме о рукопожатиях, $O$ имеет чётное число вершин. +3. Находим совершенное паросочетание $M$ минимального веса в порождённом подграфе, заданным вершинами из $O$. +4. Комбинируем рёбра $M$ и $T$ с образованием связного мультиграфа $H$, в котором каждая вершина имеет чётную степень. +5. Образуем эйлеров цикл в $H$. +6. Преобразуем цикл, найденный на предыдущем шаге, в гамильтонов цикл путём пропуска повторяющихся вершин (сокращение). + +## 5. Потоки в сетях + +### 5.1. Определение сети. Определение потока. Физический смысл. Аналогия с законами Кирхгофа. Определение разреза. Понятия потока через разрез + +#### Определение сети + +**Сеть** (англ. *flow network*) представляет собой ориентированный граф, в котором каждое ребро имеет положительную пропускную способность (англ. *capacity*). + +#### Определение потока + +**Потоком** (англ. *flow*) $f$ в $G$ является действительная функция $f: V \times V \rightarrow R$, удовлетворяющая условиям: + +1) $f(u,v) = −f(v,u)$ (антисимметричность) +2) $f(u,v) \leq c(u,v)$ (ограничение пропускной способности), если ребра нет, то $f(u,v)=0$; +3) $\Sigma v \cdot f(u,v) = 0$ для всех вершин $u$, кроме $s$ и $t$ (закон сохранения потока). + +**Величина потока** $f$ определяется как $|f|=\Sigma v \in V \cdot f(s,v)$. + +#### Физический смысл. Аналогия с законами Кирхгофа + +#### Определение разреза + +$(s,t)$ - **разрезом** (англ. *s-t cut*) $\langle S,T \rangle$ в сети $G$ называется пара множеств $S$, $T$, удовлетворяющих условиям: $s \in S$,$t \in T$ и $S = V \setminus T$. + +#### Понятия потока через разрез + +**Поток в разрезе** (англ. *flow in the cut*) $\langle S,T \rangle$ обозначается $f(S,T)$ и вычисляется по формуле: $f(S,T)= \Sigma u \in S \cdot \Sigma v \in T \cdot f(u,v)$. + +### 5.2. Доказательство факта, что поток через любой разрез одинаковый + +$f(S,T) = f(S,V) − f(S,S) = f(S,V) = f(S \setminus s,V) + f(s,V) = f(s,V) = |f|$ + +1-е равенство выполняется, так как суммы не пересекаются: $f(S,V)=f(S,S)+f(S,T)$ +2-е равенство выполняется из-за антисимметричности: $f(S,S)=−f(S,S)=0$ +3-е равенство выполняется, как и 1-е, из-за непересекающихся сумм +4-е равенство выполняется из-за сохранения потока + +### 5.3. Понятие остаточной сети. Понятие дополняющего пути. Необходимость отсутствия дополняющего пути для максимальности потока + +#### Понятие остаточной сети + +Если задана некоторая транспортная сеть $G$ и поток $f$, то **остаточная сеть** $G_f$ - это сеть, состоящая из ребер с пропускными способностями, указывающими, как могут меняться потоки через ребра $G$. + +#### Понятие дополняющего пути + +Для заданной транспортной сети и потока **дополняющим путем** (англ. *augmenting path*) является простой путь из истока в сток в остаточной сети. + +#### Необходимость отсутствия дополняющего пути для максимальности потока + +В случае существования дополняющего пути поток можно увеличить. + +### 5.4. Теорема Форда-Фалкерсона + +**Теорема Форда — Фалкерсо́на** — теорема о максимальном потоке в графе, тесно связанная с теоремой Менгера. Звучит так: величина максимального потока в графе путей равна величине пропускной способности его минимального разреза. + +### 5.5. Алгоритм Форда-Фалкерсона. Поиск минимального разреза. Пример целочисленной сети, в котором алгоритм работает долго + +#### Алгоритм Форда-Фалкерсона + +Идея алгоритма заключается в следующем. Изначально величине потока присваивается значение 0: $f(u,v)=0$ для всех $u, v \in V$. Затем величина потока итеративно увеличивается посредством поиска увеличивающего пути (путь от источника $s$ к стоку $t$, вдоль которого можно послать ненулевой поток). В данной статье рассматривается алгоритм, осуществляющий этот поиск с помощью обхода в глубину (dfs). Процесс повторяется, пока можно найти увеличивающий путь. + +**Неформальное описание:** + +1. Обнуляем все потоки. Остаточная сеть изначально совпадает с исходной сетью. +2. В остаточной сети находим любой путь из источника в сток. Если такого пути нет, останавливаемся. +3. Пускаем через найденный путь (он называется увеличивающим путём или увеличивающей цепью) максимально возможный поток: + 1. На найденном пути в остаточной сети ищем ребро с минимальной пропускной способностью $c_{min}$. + 2. Для каждого ребра на найденном пути увеличиваем поток на $c_{min}$, а в противоположном ему — уменьшаем на $c_{min}$. + 3. Модифицируем остаточную сеть. Для всех рёбер на найденном пути, а также для противоположных (антипараллельных) им рёбер, вычисляем новую пропускную способность. Если она стала ненулевой, добавляем ребро к остаточной сети, а если обнулилась, стираем его. +4. Возвращаемся на шаг 2. + +#### Поиск минимального разреза + +Алгоритм Форда-Фалкерсона использует поиск в ширину для нахождения увеличивающего пути в сети и постепенно увеличивает поток через этот путь, пока не будет достигнут максимальный поток. Затем алгоритм ищет минимальный разрез, основываясь на значении потока. + +### 5.6. Алгоритм Эдмондса-Карпа. Доказательство, что кратчайшее расстояние в остаточной сети не уменьшается + +#### Алгоритм Эдмондса-Карпа + +**Алгоритм Эдмондса-Карпа** является реализацией метода Форда-Фалкерсона, в которой в качестве дополняющего пути выбирается кратчайший по рёбрам путь в остаточной сети (длины всех рёбер равны 1). + +**Описание:** + +1. Положим все потоки равными нулю. Остаточная сеть изначально совпадает с исходной сетью. +2. В остаточной сети находим кратчайший путь из источника в сток. Если такого пути нет, останавливаемся. +3. Пускаем через найденный путь (он называется увеличивающим путём или увеличивающей цепью) максимально возможный поток: + 1. На найденном пути в остаточной сети ищем ребро с минимальной пропускной способностью $c_{min}$. + 2. Для каждого ребра на найденном пути увеличиваем поток на $c_{min}$, а в противоположном ему — уменьшаем на $c_{min}$. + 3. Модифицируем остаточную сеть. Для всех рёбер на найденном пути, а также для противоположных им рёбер, вычисляем новую пропускную способность. Если она стала ненулевой, добавляем ребро к остаточной сети, а если обнулилась, стираем его. +4. Возвращаемся на шаг 2. + +**Доказательство, что кратчайшее расстояние в остаточной сети не уменьшаетcя:** + +**Лемма:** + +Кратчайшее расстояние от истока до каждой вершины не уменьшается с выполнением каждой итерации, т.е. $\rm level_{i+1}[v] >= level_i[v]$, где нижний индекс обозначает номер фазы, перед которой взяты значения этих переменных. + +**Доказательство:** + +Зафиксируем произвольную фазу $i$ и произвольную вершину $v$ и рассмотрим любой кратчайший $s - v$ путь $P$ в сети $G^R_{i+1}$ (напомним, так мы обозначаем остаточную сеть, взятую перед выполнением $i+1$-ой фазы). Очевидно, длина пути $P$ равна $\rm level_{i+1}[v]$. + +Заметим, что в остаточную сеть $G^R_{i+1}$ могут входить только рёбра из $G^R$, а также рёбра, обратные рёбрам из $G^R$ (это следует из определения остаточной сети). Рассмотрим два случая: + +Путь $P$ содержит только рёбра из $G^R$. Тогда, понятно, длина пути $P$ больше либо равна $level_i[v]$ (потому что $level_i[v]$ по определению — длина кратчайшего пути), что и означает выполнение неравенства. Путь $P$ содержит как минимум одно ребро, не содержащееся в $G^R$ (но обратное какому-то ребру из $G^R$). Рассмотрим первое такое ребро; пусть это будет ребро $(u,w)$. $s \rightarrow u \rightarrow w$ + +Мы можем применить нашу лемму к вершине $u$, потому что она подпадает под первый случай; итак, мы получаем неравенство $\rm level_{i+1}[u] >= level_i[u]$. + +Теперь заметим, что поскольку ребро $(u, w)$ появилось в остаточной сети только после выполнения $i$-ой фазы, то отсюда следует, что вдоль ребра $(w, u)$ был дополнительно пропущен какой-то поток; следовательно, ребро $(w, u)$ принадлежало слоистой сети перед $i$-ой фазой, а потому $\rm level_i[u] = level_i[w] + 1$. Учтём, что по свойству кратчайших путей $\rm level_{i+1}[w] = level_{i+1}[u] + 1$, и объединяя это равенство с двумя предыдущими неравенствами, получаем: +$\rm level_{i+1}[w] \geq level_i[w] + 2$. + +Теперь мы можем применять те же самые рассуждения ко всему оставшемуся пути до $v$ (т.е. что каждое инвертированное ребро добавляет к $\rm level$ как минимум два), и в итоге получим требуемое неравенство. + +### 5.7. Общая оценка времени работы алгоритма Эдмондса-Карпа + +Сложность алгоритма Эдмондса-Карпа равна $O(VE^2)$. + +### 5.8. Слоистая сеть. Алгоритм Диница. Оценка времени работы без доказательства + +#### Слоистая сеть + +**Слоистая сеть** — это сеть состоящая только из таких рёбер $(v, u)$ сети $G$, что $d(v) + 1 = d(u)$, где $d$ - расстояние от истока. + +#### Алгоритм Диница + +**Схема алгоритма:** + +1. Построить слоистую сеть по остаточной, если сток не достижим то завершить алгоритм +2. Пустить блокирующий поток в слоистой сети +3. Если алгоритм завершился, то в остаточной сети сток не достижим из истока, а значит не существует увеличивающего пути, следовательно найденный поток максимален. + +#### Оценка времени работы без доказательства + +Так как расстояние строго увеличивается количество итераций алгоритма = $O(V)$. В зависимости от выбора алгоритма поиска блокирующего потока время данного алгоритма может составить $O(VE^2), O(V^2E)$ или $O(V^3)$. + +## 6. RMQ. Sparse-table, дерево отрезков. LCA. Декартово дерево по неявному ключу + +### 6.1. RSQ и RMQ. Sparse-table + +#### RSQ и RMQ + +Дан числовой массив: $A[0]$, $A[1]$, ..., $A[N−1]$.\$ + +Нужно отвечать на запросы: + +- для данных $L$ и $R$ вычислить сумму $A[L] + ... + A[R−1]$; +- (опционально) обновить элемент: $A[i] := x$. + +Операция — не обязательно сумма, а элементы — не обязательно числа. + +- Сумма $\rightarrow$ RSQ (range sum query) +- Минимум $\rightarrow$ RMQ (range minimum query) + +### 6.2. Дерево отрезков. Обработка запросов от листьев. Обработка запросов от корня + +#### Дерево отрезков + +Дерево отрезков — это структура данных, которая позволяет эффективно (т.е. за асимптотику $O(log n)$) реализовать операции следующего вида: нахождение суммы/минимума элементов массива в заданном отрезке ($a[l ... r]$, где $l$ и $r$ поступают на вход алгоритма), при этом дополнительно возможно изменение элементов массива: как изменение значения одного элемента, так и изменение элементов на целом подотрезке массива (т.е. разрешается присвоить всем элементам $a[l ... r]$ какое-либо значение, либо прибавить ко всем элементам массива какое-либо число). + +Вообще, дерево отрезков — очень гибкая структура, и число задач, решаемых ей, теоретически неограниченно. Помимо приведённых выше видов операций с деревьями отрезков, также возможны и гораздо более сложные операции (см. раздел "Усложнённые версии дерева отрезков"). В частности, дерево отрезков легко обобщается на большие размерности: например, для решения задачи о поиске суммы/минимума в некотором подпрямоугольнике данной матрицы (правда, уже только за время $O(log^2 n)$). + +Важной особенностью деревьев отрезков является то, что они потребляют линейный объём памяти: стандартному дереву отрезков требуется порядка $4n$ элементов памяти для работы над массивом размера $n$. + +#### Обработка запросов от листьев. Обработка запросов от корня + +В дереве отрезков обработка запросов может осуществляться как от листьев (bottom-up), так и от корня (top-down). Это связано с тем, как информация распространяется вдоль дерева при выполнении запросов. + +1. **Обработка запросов от листьев (Bottom-Up):** + - Идея заключается в том, что сначала вычисляются значения для листьев (первоначальные элементы массива), а затем эта информация постепенно передается вверх по дереву для расчета значений на более высоких уровнях. + - Когда поступает запрос на определенный отрезок, начинаются вычисления от соответствующих листьев. Затем результаты объединяются на более высоких уровнях дерева, пока не достигнется корень. + - Этот подход обеспечивает эффективность расчетов для запросов на отрезках. +2. **Обработка запросов от корня (Top-Down):** + - Здесь запрос начинается с корня дерева, и информация передается вниз по дереву до тех пор, пока не достигнут нужные листья (или пока не выполнены условия запроса). + - Этот метод может быть полезен в случаях, когда запросы связаны с каким-то условием, которое позволяет пропускать некоторые поддеревья, не вычисляя их полностью. + - Позволяет оптимизировать запросы, если известно, что результаты на некоторых уровнях дерева не повлияют на результат запроса. + +Реализация обработки запросов от листьев или от корня зависит от конкретных требований задачи и структуры дерева отрезков. В некоторых случаях может быть полезно использовать комбинацию обоих методов для оптимизации производительности и удовлетворения специфических требований запросов. + +### 6.3. Изменение значения в массиве, обновление дерева отрезков. Множественные операции + +#### Изменение значения в массиве, обновление дерева отрезков + +Напомним, что запрос обновления получает на вход индекс $i$ и значение $x$, и перестраивает дерево отрезков таким образом, чтобы оно соответствовало новому значению $A[i] := x$. Этот запрос должен также выполняться за время $O(log n)$. + +Это более простой запрос по сравнению с запросом подсчёта суммы. Дело в том, что элемент $A[i]$ участвует только в относительно небольшом числе вершин дерева отрезков: а именно, в $O(log n)$ вершинах — по одной с каждого уровня. + +Тогда понятно, что запрос обновления можно реализовать как рекурсивную функцию: ей передаётся текущая вершина дерева отрезков, и эта функция выполняет рекурсивный вызов от одного из двух своих сыновей (от того, который содержит позицию $i$ в своём отрезке), а после этого — пересчитывает значение суммы в текущей вершине точно таким же образом, как мы это делали при построении дерева отрезков (т.е. как сумма значений по обоим сыновьям текущей вершины). + +```c++ +void update (int v, int tl, int tr, int pos, int new_val) { + if (tl == tr) + t[v] = new_val; + else { + int tm = (tl + tr) / 2; + if (pos <= tm) + update (v*2, tl, tm, pos, new_val); + else + update (v*2+1, tm+1, tr, pos, new_val); + t[v] = t[v*2] + t[v*2+1]; + } +} +``` + +#### Множественные операции + +Множественные операции в дереве отрезков относятся к расширенной концепции структуры данных "дерево отрезков", которая позволяет эффективно выполнять несколько операций на отрезках массива или последовательности данных. Обычно дерево отрезков предназначено для выполнения операций типа запроса на сумму, минимум, максимум на определенном подотрезке массива. Однако, при необходимости, можно расширить возможности дерева отрезков для поддержки нескольких операций. + +Примеры множественных операций в дереве отрезков могут включать в себя: + +1. **Сумма и обновление (Range Sum Query and Range Update):** Помимо запросов на сумму элементов на определенном отрезке, можно добавить операции обновления, изменяющие значения элементов на определенном отрезке. +2. **Минимум/максимум и обновление (Range Minimum/Maximum Query and Range Update):** Поддержка запросов на поиск минимума или максимума на определенном отрезке, а также операций обновления значений элементов. +3. **Подсчет количества элементов в диапазоне с заданным условием (Count Elements in Range with a Given Condition):** Дерево отрезков может быть настроено для подсчета количества элементов, удовлетворяющих определенному условию, на определенном отрезке. +4. **Операции нахождения k-го порядкового статистического элемента (K-th Order Statistics Operations):** Поиск $k$-го минимального/максимального элемента на определенном отрезке. +5. **Сложные агрегатные функции:** Различные агрегатные функции, такие как нахождение среднего значения, медианы и др., могут быть реализованы с использованием множественных операций. + +Реализация множественных операций требует изменений в структуре дерева отрезков и логике обработки запросов и обновлений. Это может потребовать более сложного кода, но в то же время предоставляет более широкий спектр возможностей для решения различных задач. + +### 6.4. LCA. Метод двоичного подъёма + +#### LCA + +Наименьший общий предок (нижайший общий предок) вершин $u$ и $v$ в корневом дереве $T$ — наиболее удалённая от корня дерева вершина, лежащая на обоих путях от $u$ и $v$ до корня, т. е. являющаяся предком обеих вершин. Общепринятое сокращение — $LCA$ от англ. *lowest (least) common ancestor*. + +#### Метод двоичного подъёма + +**Метод двоичного подъёма** — один из самых простых методов для решения задачи LCA в online. Он не использует метод решения задачи RMQ и основан на методе динамического программирования. + +```c++ +function preprocess(): + int[] p = dfs(0) + for i = 1 to n + dp[i][0] = p[i] + for j = 1 to log(n) + for i = 1 to n + dp[i][j] = dp[dp[i][j - 1]][j - 1] + + int lca(int v, int u): + if d[v] > d[u] + swap(v, u) + for i = log(n) downto 0 + if d[dp[u][i]] - d[v] >= 0 + u = dp[u][i] + if v == u + return v + for i = log(n) downto 0 + if dp[v][i] != dp[u][i] + v = dp[v][i] + u = dp[u][i] + return p[v] +``` + +### 6.5. Декартово дерево по неявному ключу. Интерфейс быстрого массива: Доступ к элементу в позиции i, Вставка элемента в позицию i, Удаление элемента из позиции i, Конкатенация двух массивов, разделение массива на два + +#### Декартово дерево по неявному ключу + +Декартово дерево по неявному ключу - это структура данных, объединяющая свойства декартова дерева и быстрого массива (*cartesian tree on implicit key*). Декартово дерево основано на двух параметрах: приоритете (обычно случайном) и ключе элемента. В случае декартова дерева по неявному ключу, элементы упорядочены по порядку, заданному индексами массива. + +#### Интерфейс быстрого массива + +1. **Доступ к элементу в позиции i:** + - Так как элементы упорядочены по неявному ключу (порядковому номеру), доступ к элементу в позиции i выполняется, обращаясь к элементу с ключом i. +2. **Вставка элемента в позицию i:** + - Вставка элемента в позицию i может быть выполнена путем создания нового узла с соответствующим приоритетом и неявным ключом i, затем соединения этого нового узла с существующими поддеревьями вокруг позиции i. +3. **Удаление элемента из позиции i:** + - Удаление элемента из позиции i подразумевает разделение декартова дерева на два поддерева: одно до позиции i и второе после позиции i. Затем оба поддерева могут быть объединены, игнорируя узел с ключом i. + +Декартово дерево по неявному ключу обеспечивает эффективный доступ к элементам по индексу, а также эффективную вставку и удаление элементов, поддерживая при этом свойства декартова дерева. + +#### Конкатенация двух массивов + +Декартово дерево можно использовать для эффективной конкатенации двух массивов и разделения массива на две части, представляя массив в виде декартова дерева по неявному ключу. + +Для конкатенации двух массивов, представленных в виде декартова дерева, можно выполнить следующие шаги: + +1. Создать новый узел, который станет корнем нового дерева. +2. Как корень нового дерева выбрать узел с максимальным приоритетом из обоих деревьев. +3. Сделать корень дерева с максимальным приоритетом корнем нового дерева. +4. Поддерево с меньшим приоритетом становится левым (или правым) поддеревом корня нового дерева. +5. Рекурсивно повторить процесс для поддеревьев, соответствующих двум исходным деревьям. + +#### Разделение массива на два + +Для разделения массива на две части в декартовом дереве, можно воспользоваться тем фактом, что приоритеты узлов определяют порядок элементов. Мы ищем узел, чей неявный ключ соответствует индексу разделения. + +1. Выполняем поиск в декартовом дереве по индексу разделения. +2. Получаем поддерево с корнем, у которого неявный ключ равен индексу разделения. +3. Разделяем это поддерево на два: левое поддерево становится левой частью, а правое поддерево становится правой частью. diff --git a/doc/theory.pdf b/doc/theory.pdf new file mode 100644 index 0000000..404281b Binary files /dev/null and b/doc/theory.pdf differ