Граф#7618

Граф (Розбір)

Лексикографічно найменше топологічне сортування будується алгоритмом Кана з чергою з пріоритетом: на кожному кроці серед усіх доступних вершин (вершин, у які не входить жодне ребро з ще не виписаних вершин) обирається вершина з найменшим номером.

Додане ребро u \to v робить вершину недоступною доти, доки не буде виписано вершину . Отже, додаючи ребра, ми можемо "ховати" малі вершини й тим самим змушувати алгоритм обирати вершини з більшими номерами. Кожна схована вершина коштує одне ребро із запасу .

Жадібна ідея. Будуватимемо відповідь позиція за позицією. Лексикографічне порівняння визначається першою позицією, на якій послідовності відрізняються, тому на кожній позиції треба поставити вершину з найбільшим можливим номером — жодні виграші на наступних позиціях не компенсують програш на поточній.

Розділимо вершини, які можна виписати на поточному кроці, на дві множини:

Множину зберігаємо в черзі з пріоритетом з найменшим елементом на вершині (min-купа), а множину — у черзі з пріоритетом з найбільшим елементом на вершині (max-купа).

Коли вигідно відкладати вершину. Нехай — найменша вершина в . Якщо її не чіпати, на поточну позицію стане . Відкладати (витрачати ребро) має сенс у двох випадках:

В усіх інших випадках відкладання не збільшує число на поточній позиції, і витрачати ребро не потрібно. Відкладаємо вершини по одній, починаючи з найменшої, доки це вигідно і доки не вичерпано запас .

Яку вершину виписати. Після відкладання:

Потім, як зазвичай в алгоритмі Кана, зменшуємо вхідні степені сусідів виписаної вершини; вершини, вхідний степінь яких стає нульовим, додаються до .

Чому відповідь коректна. Кожну відкладену вершину рано чи пізно буде виписано з , і саме в цей момент додається рівно одне ребро. Тому кількість доданих ребер дорівнює кількості відкладань і не перевищує . Усі додані ребра йдуть з вершини, виписаної раніше, у вершину, виписану пізніше, тому знайдений порядок є топологічним сортуванням нового графа, і циклів не виникає.

Складність. Кожна вершина потрапляє до не більше одного разу і до не більше одного разу, а кожне ребро графа переглядається один раз. Загальна складність становить O((n + m) \log n).

Алгоритм розв'язання задачі

Використовуємо алгоритм Кана і дві черги з пріоритетом:

Оголосимо такі масиви:

Крок 1. Ініціалізація

  1. Обчислити вхідний степінь кожної вершини.

  2. Додати до усі вершини з нульовим вхідним степенем.

  3. Ініціалізувати і список порожніми.

Крок 2. Побудова відповіді

Доки не оброблено всі вершин:

1. Відкласти вершину, якщо це вигідно.

Доки кількість доданих ребер менша за , купа не порожня і виконується хоча б одна з умов:

Вилучити найменшу вершину з і додати її до . Цю вершину буде оброблено пізніше.

2. Обрати наступну вершину.

Додати у кінець .

3. Оновити граф.

Для кожного вихідного ребра v \to u:

  1. Зменшити indeg[u] на одиницю.

  2. Якщо indeg[u] став рівним нулю, додати до .

Повторювати ці кроки, доки всі вершини не потраплять до .

Крок 3. Виведення

  1. Вивести послідовність .

  2. Вивести кількість доданих ребер.

  3. Вивести самі додані ребра.

Приклад

Розглянемо приклад з умови: n = 5, k = 2, ребра 1 \to 4, 4 \to 2, 1 \to 3. Початкові вхідні степені: вершини і мають вхідний степінь , вершини — вхідний степінь . Початкові множини: A = \{1, 5\}, B = \{\,\}.

Крок

Відкладаємо

Беремо

Ребро

Порядок

(в дві вершини)

з

—

—

з

5 \to 1

(в дві вершини: і )

з

—

— (k = 0)

з

—

5, 1, 4, 2

—

з

2 \to 3

5, 1, 4, 2, 3

Розглянемо кроки докладніше.

Отримуємо порядок 5, 1, 4, 2, 3 і додані ребра 5 \to 1 та 2 \to 3. Початковий граф (ліворуч) і граф з доданими ребрами (праворуч, додані ребра позначено червоним):

Без доданих ребер лексикографічно найменше сортування таке: 1, 3, 4, 2, 5: вершина ні з чим не пов'язана, але через великий номер обирається останньою. Після додавання двох ребер вершина стає першою, і порядок 5, 1, 4, 2, 3 лексикографічно більший.

Розглянемо ще один приклад, у якому на початку доступні чотири вершини. Нехай n = 8, m = 6, k = 4, а ребра графа такі: 6 \to 3, 4 \to 8, 3 \to 5, 1 \to 7, 2 \to 7, 5 \to 7.

У вершини 1, 2, 4, 6 не входить жодне ребро, тому на початку A = \{1, 2, 4, 6\}, B = \{\,\}. Без доданих ребер лексикографічно найменше сортування таке: 1, 2, 4, 6, 3, 5, 7, 8.

Початковий граф (ліворуч) і граф після додавання ребер (праворуч, додані ребра позначено червоним)

Крок

до

до

Відкладаємо

Беремо

Ребро

\{1, 2, 4, 6\}

з

—

\{4, 2, 1\}

3~(4 > 3)

з

6 \to 4

\{3, 2, 1\}

—

з

—

\{3, 2, 1\}

—

з

8 \to 3

—

з

—

—

з

5 \to 2

—

з

2 \to 1

—

з

—

Розглянемо кроки докладніше.

Відповідь:

6 4 8 3 5 2 1 7
4
6 4
8 3
5 2
2 1

Цей приклад добре ілюструє всі випадки роботи алгоритму. На кроці відкладаються одразу три вершини — доки містить щонайменше дві вершини. На кроці спрацьовує друга умова: містить лише вершину , але містить більшу вершину , тому теж відкладається. Відкладені вершини залишають у порядку спадання (4, 3, 2, 1), і кожна з них отримує рівно одне ребро з вершини, виписаної перед нею. Тому ребер додано рівно стільки, скільки було відкладань, — чотири.

Реалізація алгоритму

Читаємо вхідні дані. Будуємо граф. Списки суміжності зберігаємо в , вхідні степені — в .

scanf("%d %d %d", &n, &m, &k);
vector<vector<int>> g(n + 1);
vector<int> indeg(n + 1, 0);
for (int i = 0; i < m; i++)
{
  scanf("%d %d", &u, &v);
  g[u].push_back(v);
  indeg[v]++;
}

Оголосимо дві черги з пріоритетом: — доступні вершини, з найменшим елементом на вершині (min-купа); — відкладені вершини, з найбільшим елементом на вершині (max-купа).

priority_queue<int, vector<int>, greater<int>> A; // доступні, min-купа
priority_queue<int> B;                            // відкладені, max-купа

Спочатку доступні всі вершини з нульовим вхідним степенем.

for (int v = 1; v <= n; v++)
  if (indeg[v] == 0) A.push(v);

У масиві накопичуємо відповідь — шукане топологічне сортування, а в масиві зберігаємо додані ребра.

vector<int> order;
order.reserve(n);
vector<pair<int, int>> added;

Кожна ітерація циклу додає до відповіді одну вершину, доки не буде виписано всі вершин.

while ((int)order.size() < n)
{

Відкладаємо вершини. Доки в запасі залишаються ребра і множина не порожня, розглядаємо найменшу доступну вершину . Її вигідно відкласти, якщо містить ще хоча б одну вершину або якщо містить відкладену вершину з більшим номером. Відкладена вершина переноситься з до , а запас зменшується на . Інакше відкладання припиняється.

  // відкладаємо вершини, доки це вигідно і є запас ребер
  while (k > 0 && !A.empty())
  {
    int x = A.top();
    if (A.size() >= 2 || (!B.empty() && B.top() > x))
    {
      A.pop();
      B.push(x);
      k--;
    }
    else break;
  }

Обираємо вершину , яку буде виписано на поточну позицію. Якщо не порожня, це найменша доступна вершина. Інакше беремо найбільшу відкладену вершину і додаємо ребро з останньої виписаної вершини order.back() у .

  int v;
  if (!A.empty())
  {
    v = A.top();
    A.pop();
  }
  else
  {
    v = B.top();
    B.pop();
    added.push_back(make_pair(order.back(), v));
  }

Дописуємо до відповіді і, як в алгоритмі Кана, зменшуємо вхідні степені її сусідів. Вершини, вхідний степінь яких став нульовим, стають доступними.

  order.push_back(v);
  for (int to : g[v])
    if (--indeg[to] == 0) A.push(to);
}

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

for (int i = 0; i < n; i++)
  printf("%d ", order[i]);
printf("\n%d\n", added.size());
for (auto &e : added)
  printf("%d %d\n", e.first, e.second);