Блог пользователя Igor_Parfenov

Автор Igor_Parfenov, история, 10 дней назад, По-русски

2040A - Игра деления

Разбор
Решение C++
Решение Python

2040B - Раскрасить полоску

Разбор
Решение C++
Решение Python
Замечания

2040C - Упорядоченные перестановки

Разбор
Решение C++
Решение Python

2040D - Непростое дерево

Разбор
Решение 1
Решение 2 (zap4eg)
Замечания

2040E - Контроль случайности

Разбор
Решение
Решение (Замечания)
Замечания

2040F - Количество кубов

Разбор
Решение Phi
Решение DP

Полный текст и комментарии »

Разбор задач Codeforces Round 992 (Div. 2)
  • Проголосовать: нравится
  • +104
  • Проголосовать: не нравится

Автор Igor_Parfenov, история, 11 дней назад, перевод, По-русски

Привет!

В 08.12.2024 17:35 (Московское время) состоится Codeforces Round 992 (Div. 2).

Задачи написал и подготовил Igor_Parfenov.

Я хотел бы поблагодарить всех, кто сделал этот раунд возможным:

Этот раунд будет рейтинговым для участников с рейтингом менее 2100.

У вас будет 2 часа для решения 6 задач.

Разбалловка: 500 — 1000 — 1500 — 2000 — 2250 — 2750.

Удачи!

UPD: Разбор

UPD: Поздравления победителям!

Div.2:

  1. daniel6202

  2. younesg

  3. houseof

  4. HUST_USELESS

  5. FatihCihan

Div.1 + Div.2:

  1. jiangly

  2. maspy

  3. Rubikun

  4. BurnedChicken

  5. neal

Полный текст и комментарии »

  • Проголосовать: нравится
  • +250
  • Проголосовать: не нравится

Автор Igor_Parfenov, история, 5 недель назад, По-русски

Добрый день. Пару месяцев назад я закончил написание книги и выставляю её в открытый доступ: GitHub

В чём идея? Контент этой книги представляет собой не только обучение языку C, но и обучение большому количеству прикладных вещей с серьёзной глубиной погружения. Я постарался дать ответ на как можно большее количество вопросов, которые возникают в процессе изучения, оставив минимальное количество дыр в понимании, как всё работает.

Посмотреть .html файл книги можно на GitHub-е. Но он не рендерит MathJax, поэтому лучше скачать файл c-book.html локально. Можно как угодно (в том числе в issues) сообщать мне о нерабочем коде в примерах, опечатках, неправильных утверждениях с моей стороны. Буду также рад увидеть конструктивную критику. Возможно, в ответ на неё, я буду добавлять новые главы в книгу.

(Эта книга абсолютно точно не является рекламой Zig-а.)

Полный текст и комментарии »

  • Проголосовать: нравится
  • +14
  • Проголосовать: не нравится

Автор Igor_Parfenov, история, 20 месяцев назад, перевод, По-русски

1823A - A-характеристика

Разбор
Решение C++
Решение Python

1823B - Шаговая сортировка

Разбор
Решение C++
Решение Python

1823C - Сильно составные

Разбор
Решение C++
Решение Python
Замечания

1823D - Уникальные палиндромы

Разбор
Решение C++
Решение Python
Замечания

1823E - Удалить граф

Разбор
Решение C++
Решение Python

1823F - Случайная прогулка

Разбор
Решение C++
Решение Python

Полный текст и комментарии »

Разбор задач Codeforces Round 868 (Div. 2)
  • Проголосовать: нравится
  • +106
  • Проголосовать: не нравится

Автор Igor_Parfenov, история, 20 месяцев назад, перевод, По-русски

Привет!

В 27.04.2023 17:35 (Московское время) состоится Codeforces Round 868 (Div. 2).

Задачи написал и подготовил Igor_Parfenov.

Я хотел бы поблагодарить всех, кто сделал этот раунд возможным:

Этот раунд будет рейтинговым для участников с рейтингом менее 2100.

У вас будет 2 часа для решения 6 задач.

Разбалловка: 500 — 1000 — 1250 — 2000 — 2000 — 2500.

Удачи!

UPD: Разбор

UPD: Поздравления победителям!

Div.2:

  1. Low-Deny-Cup

  2. psz6

  3. BULBULBUL

  4. Epyset

  5. chenguoyi

Div.1 + Div.2:

  1. SSRS_

  2. maspy

  3. Rubikun

  4. heno239

  5. Crystally

Полный текст и комментарии »

  • Проголосовать: нравится
  • +350
  • Проголосовать: не нравится

Автор Igor_Parfenov, история, 2 года назад, По-английски
Code1

Look at this code. It seems, the expected output is always 1. However, in

  • Codeforces
    • G++20
    • G++17
    • G++14
    • VC++2017
  • in my local Windows G++ 8.1.0
  • in my local Debian G++ 10.2.1

the output is always 0. In Codeforces::Clang++17 & Clang++20 the output is

Clang++
Code2

Now look at this code. Here in all previously mensioned compilers (including Clang++) the output is always 1.

How found

Полный текст и комментарии »

  • Проголосовать: нравится
  • +39
  • Проголосовать: не нравится

Автор Igor_Parfenov, история, 4 года назад, По-русски

В этом посте я подробно объясню принцип работы алгоритма Быстрого преобразования Фурье. Будет необходимо знание базовых операций с комплексными числами. Здесь приложена краткая реализация комплексных чисел.

Комплексные числа

Задача

Даны два полинома: $$$A = a_{0} + a_{1} \cdot x + \dots + a_{n-1} \cdot x^{n-1}$$$ и $$$B = b_{0} + b_{1} \cdot x + \dots + b_{n-1} \cdot x^{n-1}$$$. Для удобства будем записывать их как $$$[a_{0}, a_{1}, \cdot a_{n-1}]$$$ и $$$[b_{0}, b_{1}, \cdot b_{n-1}]$$$. Необходимо найти полином $$$C = A \cdot B$$$. Его размер равен $$$2n$$$.

Тривиальное решение

Задачу можно решить за асимптотику $$$O(n^{2})$$$, напрямую посчитав $$$C = A \cdot B = [a_{0} \cdot b_{0}, a_{0} \cdot b_{1} + a_{1} \cdot b_{0}, \dots ] = [\sum\limits_{i \in [0,0]}(a_{i} \cdot b_{0-i}), \sum\limits_{i \in [0,1]}(a_{i} \cdot b_{1-i}), \dots, \sum\limits_{i \in [0,k]}(a_{i} \cdot b_{k-i}), \dots]$$$.

План решения

Решение с помощью быстрого преобразования Фурье будет состоять из трех шагов:

  1. Вычислить $$$A(x_{0}), A(x_{1}), \dots A(x_{2n-1})$$$ и $$$B(x_{0}), B(x_{1}), \dots B(x_{2n-1})$$$.

  2. Вычислить значение $$$C$$$ в точках: $$$C(x_{0}) = A(x_{0}) \cdot B(x_{0}), C(x_{1}) = A(x_{1}) \cdot B(x_{1}), \dots C(x_{2n-1}) = A(x_{2n-1}) \cdot B(x_{2n-1})$$$.

  3. Интерполировать $$$C$$$ по известным $$$2n$$$ значениям.

Вычисление значения полинома в точке в общем случае решается за $$$O(n)$$$. Поэтому первый шаг требует $$$O(n^{2})$$$ действий. Второй шаг решается одним проходом по массивам за $$$O(n)$$$. Интерполяция полинома решается в общем случае за $$$O(n^{2})$$$ с помощью интерполяционной формулы Лагранжа. Итоговая асимптотика решения для произвольных $$$x_{0}, x_{1}, \dots x_{2n-1}$$$ — $$$O(n^{2})$$$. Количество действий может быть существенно уменьшено, если выбрать $$$x_{0}, x_{1}, \dots x_{2n-1}$$$ особым образом.

Выбор множества для $$$X$$$

Рассмотрим множество $$$S$$$ комплексных чисел, модуль которых равен $$$1$$$. На комплексной плоскости ГМТ множества $$$S$$$ — окружность радиуса $$$1$$$ с центром в начале координат. Будем обозначать элемент множества $$$S$$$, аргумент которого равен $$$\phi$$$, как $$$\overline{\phi}$$$. Данное множество обладает замечательным свойством, на которое мы и будем опираться при написании алгоритма:

Теорема: Для $$$n \in \mathbb{Z}$$$ верно $$$(\overline{\phi})^{n} = \overline{n\phi}$$$.

Доказательство: докажем с помощью математической индукции. Для $$$n=0$$$ утверждение очевидно. Пусть утверждение верно для $$$n=k$$$. Докажем, что оно верно для $$$n=k+1$$$ и для $$$n=k-1$$$. $$$(\overline{\phi})^{k+1} = (\overline{\phi})^{k} \cdot \overline{\phi} = \overline{k\phi} \cdot \overline{\phi}$$$. Известно, что при перемножении комплексных чисел их модули перемножаются, а аргументы складываются. Поэтому $$$\overline{k\phi} \cdot \overline{\phi} = \overline {(k+1)\phi}$$$. Аналогично $$$(\overline{\phi})^{k-1} = \overline{k\phi} / \overline{\phi}$$$. Известно, что при делении комплексных чисел их модули делятся, а аргументы вычитаются. Поэтому $$$\overline{k\phi} / \overline{\phi} = \overline {(k-1)\phi}$$$.

Основной смысл этой теоремы — тот факт, что мы можем заменить степени на суммы. Кроме того, если выбрать в качестве множества $$$X$$$ точек числа $$$\overline{\frac{0 \cdot 2\pi}{2n}}, \overline{\frac{1 \cdot 2\pi}{2n}}, \dots \overline{\frac{(2n-1) \cdot 2\pi}{2n}}$$$, то любое число из этого множества в произвольной целой степени будет в этом множестве (говорят, множество $$$X$$$ замкнуто относительно операции целой степени).

Быстрое преобразование Фурье

Задача: дан многочлен $$$A = [a_{0}, a_{1}, \dots a_{n-1}]$$$. Необходимо вычислить $$$A(\overline{\frac{0 \cdot 2\pi}{n}}), A(\overline{\frac{1 \cdot 2\pi}{n}}), \dots A(\overline{\frac{(n-1) \cdot 2\pi}{n}})$$$. (Важно: выше мы вычисляли значения функция в $$$2n$$$ точках, так как нам необходимо знать $$$2n$$$ значений полинома $$$C$$$. Сейчас же мы вычислим значения в $$$n$$$ точках, а перед вызовом этого алгоритма просто допишем к $$$A$$$ $$$n$$$ нулей.) Для удобства будем считать $$$n$$$ целой степенью числа $$$2$$$.

Решение:

Будем решать задачу рекурсивно. Для $$$n=1$$$ нам необходимо вычислить массив из одного элемента $$$A(\overline{0}) = A(1) = a_{0}$$$. То есть, нам нужно вернуть массив без изменений.

Пусть теперь $$$n \ge 2$$$. Заметим, что $$$A(x) = a_{0}x^{0} + a_{1}x^{1} + \dots + a_{n-1}x^{n-1} = (a_{0} + a_{2}x^{2} + a_{4}x^{4} + \dots + a_{n-2}x^{n-2}) + x(a_{1} + a_{3}x^{2} + a_{5}x^{4} + \dots + a_{n-1}x^{n-2})$$$. Пусть $$$A_{1} = [a_{0}, a_{2}, a_{4}, \dots a_{n-2}]$$$ и $$$A_{2} = [a_{1}, a_{3}, a_{5}, \dots a_{n-1}]$$$. Тогда $$$A(x) = A_{1}(x^{2}) + x A_{2}(x^2)$$$. Кажется, что члены $$$x^{2k}$$$ усложняют задачу, но согласно теореме выше это $$$2kx$$$! Поэтому $$$A(x) = A_{1}(2x) + x A_{2}(2x)$$$.

Предположим, у нас есть алгоритм, решающий задачу для $$$n=\frac{k}{2}$$$. Решим задачу для $$$n=k$$$. Построим массивы $$$A_{1}$$$ и $$$A_{2}$$$ по определению выше и вызовем БПФ для них. Согласно предположению, мы умеем это делать. Теперь необходимо простроить БПФ для $$$A$$$. Пусть в массивах $$$B_{1}$$$ и $$$B_{2}$$$ лежат БПФ от них $$$A_{1}$$$ и $$$A_{2}$$$. Вспомним, что это $$$B_{1} = [A_{1}(\overline{\frac{0 \cdot 2\pi}{\frac{k}{2}}}), A_{1}(\overline{\frac{1 \cdot 2\pi}{\frac{k}{2}}}), \dots A_{1}(\overline{\frac{(\frac{k}{2}-1) \cdot 2\pi}{\frac{k}{2}}})]$$$ и $$$B_{2} = [A_{2}(\overline{\frac{0 \cdot 2\pi}{\frac{k}{2}}}), A_{2}(\overline{\frac{1 \cdot 2\pi}{\frac{k}{2}}}), \dots A_{2}(\overline{\frac{(\frac{k}{2}-1) \cdot 2\pi}{\frac{k}{2}}})]$$$. То есть, это значения полиномов $$$A_{1}$$$ и $$$A_{2}$$$ в $$$\frac{k}{2}$$$ точках равномерно распределенных по окружности комплексной плоскости. Пусть $$$B$$$ — результат БПФ для $$$A$$$. Заполним этот массив: $$$B[i] = A(\overline{\frac{i \cdot 2\pi}{k}}) = A_{1}(\overline{\frac{2i \cdot 2\pi}{k}}) + \overline{\frac{i \cdot 2\pi}{k}} \cdot A_{2}(\overline{\frac{2i \cdot 2\pi}{k}}) = A_{1}(\overline{\frac{i \cdot 2\pi}{\frac{k}{2}}}) + \overline{\frac{i \cdot 2\pi}{k}} \cdot A_{2}(\overline{\frac{i \cdot 2\pi}{\frac{k}{2}}}) = B_{1}[i] + \overline{\frac{i \cdot 2\pi}{k}} \cdot B_{2}[i]$$$. Для $$$i \ge \frac{k}{2}$$$ мы таким образом не можем посчитать $$$B[i]$$$, так как $$$B_{1}[i]$$$ и $$$B_{2}[i]$$$ мы явно не считали. Но заметим, что $$$B_{1}[i]=B_{1}[i+\frac{qk}{2}]$$$ и $$$B_{2}[i]=B_{2}[i+\frac{qk}{2}] \; q \in \mathbb{Z}$$$, то есть, значения функций периодические с периодом $$$T=\frac{k}{2}$$$. Но тогда для $$$i \ge \frac{k}{2}$$$ мы можем посчитать значения так: $$$B[i] = B_{1}[i-\frac{k}{2}] + \overline{\frac{i \cdot 2\pi}{k}} \cdot B_{2}[i-\frac{k}{2}]$$$, или для $$$i < \frac{k}{2}$$$ имеем $$$B[i+\frac{k}{2}] = B_{1}[i] + \overline{\frac{(i - \frac{k}{2}) \cdot 2\pi}{k}} \cdot B_{2}[i] = B_{1}[i] - \overline{\frac{i \cdot 2\pi}{k}} \cdot B_{2}[i]$$$, так как если отобразить аргумент комплексного числа, оно отобразится.

Асимптотика алгоритма $$$O(n \log{n})$$$.

БПФ

Обратное быстрое преобразование Фурье

Второй шаг плана выполняется за $$$O(n)$$$, необходимо просто одним проходом по массиву посчитать $$$C[i]=A[i] \cdot B[i]$$$.

Выполним теперь быструю интерполяцию. Заметим для начала, что уже написанный алгоритм БПФ решает следующую задачу: по данному массиву $$$A$$$ он считает:

$$$[w^{0} \quad w^{0} \quad w^{0} \dots \quad w^{0n-1}] \quad [a_{0}] \quad [x_{0}]$$$
$$$[w^{0} \quad w^{1} \quad w^{2} \dots \quad w^{1n-1}] \quad [a_{1}] \quad [x_{1}]$$$
$$$[w^{0} \quad w^{2} \quad w^{4} \dots \quad w^{2n-1}] \cdot [a_{2}] = [x_{2}]$$$
$$$[w^{0} \quad w^{3} \quad w^{6} \dots \quad w^{3n-1}] \quad [a_{3}] \quad [x_{3}]$$$
$$$\dots$$$
$$$[w^{0} \quad w^{n} \quad w^{2n} \dots \quad w^{n \cdot n -1}] \quad [a_{n-1}] \quad [x_{n-1}]$$$

, где $$$w = \overline{\frac{2\pi}{n}}$$$. (Это можно увидеть, перемножив матрицы и воспользовавшись тем, что $$$\overline{\phi}^{n} = \overline{n\phi}$$$.) Перепишем равенство в виде $$$W \cdot A = X$$$. Обратите внимание, что $$$W$$$ не зависит от содержимого $$$A$$$, для фиксированного $$$n$$$ она всегда одинакова. Теперь нам нужно по известному $$$X$$$ найти $$$A$$$. Предположим, что $$$W$$$ имеет обратную. Умножим равенство с двух сторон слева на обратную: $$$W^{-1} \cdot W \cdot X = W^{-1} \cdot A$$$, откуда $$$W^{-1} \cdot A = X$$$.

Утверждение: $$$W^{-1} = \frac{1}{n} \cdot$$$

$$$[w^{-0} \quad w^{-0} \quad w^{-0} \dots \quad w^{-0n+1}]$$$
$$$[w^{-0} \quad w^{-1} \quad w^{-2} \dots \quad w^{-1n+1}]$$$
$$$[w^{-0} \quad w^{-2} \quad w^{-4} \dots \quad w^{-2n+1}]$$$
$$$[w^{-0} \quad w^{-3} \quad w^{-6} \dots \quad w^{-3n+1}]$$$
$$$\dots$$$
$$$[w^{-0} \quad w^{-n} \quad w^{-2n} \dots \quad w^{-n \cdot n +1}]$$$

Доказательство: Нам необходимо показать, что $$$W \cdot W^{-1} = I$$$. Перемножим строку $$$i$$$ матрицы $$$W$$$ со столбцом $$$j$$$ матрицы $$$nW^{-1}$$$, так, что $$$i \neq j$$$. Имеем: $$$w^{0}w^{0} + w^{i}w^{-j} + w^{2i}w^{-2j} + \dots + w^{(n-1)i}w^{(n-1)j} = \overline{\frac{0(i-j) \cdot 2\pi}{n}} + \overline{\frac{1(i-j) \cdot 2\pi}{n}} + \overline{\frac{2(i-j) \cdot 2\pi}{n}} + \dots + \overline{\frac{(n-1)(i-j) \cdot 2\pi}{n}}$$$. Но это четное количество комплексных чисел, размещенных на единичной окружности с равным шагом, поэтому их сумма равна $$$0$$$. Перемножим теперь строку $$$i$$$ матрицы $$$W$$$ со столбцом $$$i$$$ матрицы $$$nW^{-1}$$$. Имеем $$$w^{0}w^{0} + w^{i}w^{-i} + w^{2i}w^{-2i} + \dots + w^{(n-1)i}w^{(n-1)i} = \overline{\frac{0 \cdot 2\pi}{n}} + \overline{\frac{0 \cdot 2\pi}{n}} + \overline{\frac{0 \cdot 2\pi}{n}} + \dots + \overline{\frac{0 \cdot 2\pi}{n}} = n$$$.

Будем искать $$$nW^{-1}X = nA$$$, а затем поделим результат на $$$n$$$. Тот факт, что теперь все степени в матрице $$$nW^{-1}$$$ отображены означает, что и значение $$$x$$$ в формуле $$$B[i] = B_{1}[i] + \overline{\frac{i \cdot 2\pi}{k}} \cdot B_{2}[i]$$$ должен поменять знак. Так как $$$B_{1}$$$ и $$$B_{2}$$$ содержат в качестве аргумента $$$x^{2}$$$, их значение не поменяется. Тогда $$$B[i] = B_{1}[i] + \overline{-\frac{i \cdot 2\pi}{k}} \cdot B_{2}[i]$$$. Аналогично $$$B[i+\frac{n}{2}] = B_{1}[i] - \overline{-\frac{i \cdot 2\pi}{k}} \cdot B_{2}[i]$$$.

Асимптотика алгоритма $$$O(n \log{n})$$$.

Обратное БПФ

Умножение полиномов

Теперь мы готовы написать умножение полиномов за $$$O(n \log{n})$$$.

Умножение полиномов

Улучшение алгоритма БПФ

Полученный алгоритм сильно блуждает по памяти, из за чего необходимые значения плохо кэшируются. Можно алгоритм сильно ускорить, изменив принцип обхода. Посмотрим, как в процессе вычисления БПФ делится наш полином. На первом этапе в первый полином попадают числа, чей последний бит в индексе равен $$$0$$$, а во второй полином — чей бит равен $$$1$$$. На втором этапе числа делятся по второму с конца биту. Иными словами, если мы отсортируем числа в зеркально написанной двоичной форме, массив на каждом этапе будет делится ровно на две части: левая половина уйдет в первый массив, а правая — во второй.

Спойлер

Для $$$n=4$$$, эта перестановка: $$$[0,2,1,3]$$$, для $$$n=8$$$, эта перестановка: $$$[0,4,2,6,1,5,3,7]$$$.

Отсортировать числа по зеркально написанной двоичной форме можно за $$$O(n)$$$. После этого мы можем избавиться от рекурсии. Для начала разделим массив на отрезки длины $$$2$$$ и посчитаем БПФ от них, запишем результат в них же. Затем разделим массив на отрезки длины $$$4$$$ и повторим процесс. Когда мы на очередном этапе хотим посчитать БПФ на отрезке $$$[l,r)$$$, в отрезках $$$[l,m)$$$ и $$$[m,r)$$$ уже записаны их БПФ.

Ускорение БПФ

Так как мы $$$\log{n}$$$ раз проходимся по всему массиву без прыжков, данные хорошо кэшируются, и алгоритм ускоряется в $$$\sim 10$$$ раз.

Задача

Полный текст и комментарии »

  • Проголосовать: нравится
  • +22
  • Проголосовать: не нравится

Автор Igor_Parfenov, 5 лет назад, По-русски

В этом посте я расскажу о четырех основных и простых алгоритмах поиска максимального потока, к каждому алгоритму прилагается описание работы, псевдокод, нестрогое объяснение корректности, реализация на C++11, и ссылки на задачи.

I. Задача

Пусть дан граф $$$G=<V,E>$$$ и две вершины $$$s,t \in V$$$, которые в дальнейшем будем называть исток и сток, и каждому ребру из вершины $$$a$$$ в вершину $$$b$$$ сопоставлена пропускная способность $$$c_{a,b}$$$. Определим сопоставление каждому ребру $$$<a,b>$$$ числа $$$g_{a,b}$$$. Назовем это сопоставление потоком, если для каждого ребра $$$<a,b>$$$ верны два утверждения:
1) $$$0 \le g_{a,b} \le c_{a,b}$$$
2) $$$ \sum_{i \in V} g_{i,a} = \sum_{i \in V} g_{a,i} \forall a \in V \setminus \{ s,t \} $$$
Величина потока равна $$$ \sum_{i \in V} g_{s,i} = \sum_{i \in V} g_{i,t} = |F|$$$. Задача — вычислить $$$|F|$$$ и все $$$g_{a,b}$$$.

II. Метод проталкивания потока

a) Алгоритм Форда-Фалкерсона

Пусть дан граф с пропускными способностями. Будем хранить его пропускные способности в матрице смежности $$$c[][]$$$, если ребра $$$<a,b>$$$ нет в графе, $$$c[a][b]=0$$$. Определим операцию проталкивания потока: пусть мы выбрали путь $$$p_{1},p_{2}, \dots ,p_{k}$$$ из истока $$$s$$$ в сток $$$t$$$ такой, что пропускные способности между соседними вершинами положительны: $$$c_{p_{i-1},p_{i}} > 0 \forall i \in [2,k]$$$. Пусть $$$AugFlow$$$ — минимальная пропускная способность на ребре на пути: $$$AugFlow = min_{i \in [2,k]} c_{p_{i-1},p_{i}}$$$. Пройдем по этому пути и вычтем из пропускных способностей $$$AugFlow$$$ по направлению к стоку и добавим против стока. Итак, если у нас есть путь ненулевой пропускной способности, мы можем увеличить поток. Можно ли, вычисляя такие пути, всегда найти максимальный поток? Работает следующая теорема:

Теорема 1.1 Для того, чтобы поток был максимальным, необходимо и достаточно, чтобы отсутствовал путь из истока в сток ненулевой пропускной способности.

Алгоритм

Асимптотика Если пропускные способности целочисленные, то дополняющий поток всегда не меньше единицы. Тогда, так как асимптотика обхода графа $$$O(E)$$$, асимптотика алгоритма составляет $$$O(E|F|)$$$. К сожалению, это не так для графов с вещественными пропускными способностями. Но если использовать обход графа в ширину, действует следующая теорема:

Теорема 1.2 В течении алгоритма с обходом в ширину расстояние от истока до любой вершины по ребрам ненулевой пропускной способности не уменьшается.

Это дает еще одну асимптотику: $$$O(VE^{2})$$$. Данный алгоритм называется алгоритмом Эдмондса-Карпа. Его реализация здесь дана.

Реализация

Задачи 1 2 3

b) Алгоритм Диница

Алгоритм Форда-Фалкерсона можно ускорить, использую следующую идею. Выполним поиск в ширину из истока и, игнорируя ребра с нулевой пропускной способностью, вычислим расстояния $$$d[]$$$ до каждой вершины. Будем говорить, что мы построили слоистую сеть. Затем будем искать такие дополняющие пути $$$p[]$$$, что $$$d[p[1]]-d[p[0]]=d[p[2]]-d[p[1]]= \dots =1$$$. Все эти пути можно найти довольно быстро — за $$$O(VE)$$$: очевидно, что в силу ограничения, наложенного на путь, если по ребру $$$<a,b>$$$ не удалось протолкнуть путь, в течении фазы по нему нельзя будет протолкнуть путь. Поэтому будем поддерживать у каждой вершины первую нерассмотренную вершину для каждой фазы, и искать следующие пути в этой фазе, начиная с первой нерассмотренной вершины и увеличивать это значение, если путь с этой вершины не нашелся. После того, как мы нашли все пути, мы приступаем к следующей фазе. При этом работает следующая теорема:

Теорема 2.1 Расстояние до стока после каждой фазы строго возрастает.

Тогда, так как путь из истока в сток не больше $$$V$$$, количество фаз $$$O(V)$$$.

Алгоритм

Асимптотика $$$O(V)$$$ фаз и $$$O(VE)$$$ на каждую фазу — итого $$$O(V^{2}E)$$$.

Частный случай использования этого алгоритма для поиска максимального паросочетания в двудольном графе называется алгоритмом Хопкрофта-Карпа. Он имеет более сильную асимптотику $$$O(\sqrt{V}E)$$$.

Реализация

Задачи 4 5

III. Метод проталкивания предпотока

a) Разрядить, пока есть что разрядить

Назовем предпотоком сопоставление каждому ребру в графе числа, при котором выполняется:
1) $$$0 \le g_{a,b} \le c_{a,b}$$$
2) $$$ \sum_{i \in V} g_{i,a} \ge \sum_{i \in V} g_{a,i} \forall a \in V \setminus \{ s,t \} $$$
Определим переполнение $$$e_{i}$$$ вершины как значение $$$ \sum_{i \in V} g_{i,a} - \sum_{i \in V} g_{a,i} $$$, и высоту $$$h_{i}$$$ вершины. Будем выполнять две операции:
1) Проталкивание из вершины $$$a$$$ в вершину $$$b$$$. Допустимо тогда, когда $$$h_{a}=h_{b}+1$$$. Проталкиваем столько потока, на сколько переполнена $$$a$$$, но не более, чем может вместить ребро $$$<a,b>$$$: $$$pushed=min(e_{a},c_{a,b})$$$.
2) Поднятие вершины $$$a$$$: допустимо, если $$$a$$$ переполнена, но нет такой вершины $$$b$$$, что $$$h_{a}=h_{b}+1$$$.
Алгоритм заключается в использовании этих операций в любом порядке, пока хотя бы одна из них допустима. Можно для удобства определить операцию разрядить вершину, которая выполняет проталкивания из нее и поднятия, пока она переполнена (это не повлияет на асимптотику).
Перед основным циклом необходимо выполнить следующую инициализацию: проталкиваем тривиально из истока во все соседние вершины.

Алгоритм

Теорема 3.1 Подъем вершины строго увеличивает ее высоту.

Теорема 3.2 Если ни одно действие недопустимо — предпоток есть максимальный поток.

Асимптотика Высота каждой вершины во время работы алгоритма не превышает $$$2V$$$, откуда количество вызовов операции поднять есть $$$O(V^{2})$$$, а количество вызовов операции протолкнуть есть $$$O(V^{2}E)$$$. Итоговая асимптотика — $$$O(V^{2}E)$$$.

Реализация

Задачи 4 5
Те же, что и для Диница

б) Разрядить в очереди

Краткая идея: будем хранить все переполненные вершины в очереди, и разряжать первую вершину.

Алгоритм

Асимптотика $$$O(V^{3})$$$.

Реализация

Задачи 6
К сожалению, к данной задаче принимается Диниц, а не должен.

Спойлер

Полный текст и комментарии »

  • Проголосовать: нравится
  • +46
  • Проголосовать: не нравится