Втеча ватажка#9888

Сховища та ходи утворюють дерево з вершин. Перекриття ходу – це видалення ребра. Потрібно додати найменшу кількість ребер так, щоб після видалення будь-якого одного ребра граф залишався зв'язним, тобто щоб у ньому не було мостів. Якщо граф залишається зв'язним, то з будь-якого сховища можна дістатися до штабу, тому номер штабу на відповідь не впливає.

Нижня оцінка. Назвемо листком вершину степеня , і нехай у дереві листків. Єдине ребро, що виходить з листка, є мостом. Щоб воно перестало бути мостом, до листка потрібно провести хоча б одне нове ребро. Кожне нове ребро має два кінці, тому покриває не більше двох листків. Отже, знадобиться не менше нових ребер.

Побудова. Покажемо, що ребер завжди достатньо. Запустимо пошук у глибину з будь-якої вершини і випишемо листки в тому порядку, в якому проходить їхній обхід: l_0, l_1, ..., l_{L-1}. Позначимо k = \lfloor L / 2 \rfloor і з'єднаємо листки, номери яких у цьому списку відрізняються на :

l_0 - l_k, l_1 - l_{k+1}, ..., l_{\lceil L / 2 \rceil - 1} - l_{\lceil L / 2 \rceil - 1 + k}

Отримується рівно ребер, і кожен листок виявляється кінцем хоча б одного з них. При непарному листок l_k бере участь у двох ребрах.

Приклад. На малюнку L = 5 і k = 2. Листки зафарбовані, поряд з кожним вказано його номер у порядку обходу. Додані ребра (3, 5), (4, 7) і показані пунктиром, листок бере участь у двох з них.

Чому мостів не залишається. Підвісимо дерево за вершину, з якої запускався пошук. Ребро дерева (, батько ) перестає бути мостом, якщо хоча б одне нове ребро з'єднує листок всередині піддерева з листком поза ним. Пошук у глибину обходить піддерево цілком, не перериваючись, тому листки цього піддерева йдуть у списку підряд і займають відрізок довжини . У піддереві є хоча б один листок, а всі листків у ньому опинитися не можуть: або у кореня є інші піддерева зі своїми листками, або корінь сам є листком. Тому 1 \le s \le L - 1.

Партнер кожного листка в новому ребрі відстоїть від нього в списку рівно на позицій. Розглянемо два випадки:

В обох випадках знайдеться нове ребро, що з'єднує піддерево v з рештою дерева. Отже, жодне ребро не залишиться мостом.

Теорема. Найменша кількість нових ребер дорівнює . Достатньо виписати листки в порядку пошуку в глибину і з'єднати кожен листок l_i з листком l_i + \lfloor L / 2 \rfloor для i = 0, 1, ..., \lceil L / 2 \rceil - 1.

При n = 2 обидва кінці єдиного ребра є листками, і відповіддю буде друге ребро між ними.

Приклад

У першому прикладі пошук з вершини проходить листки в порядку . Тут L = 3, k = 1, і програма додає ребра і :

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

У другому прикладі листки в порядку обходу: 4, 5, 2, 3. Тут L = 4, k = 2:

0

1

2

3

l_i

4

5

2

3

З'єднуємо l_0 - l_2 і l_1 - l_3, тобто додаємо ребра і :

Перевіримо, наприклад, ребро . Листки піддерева вершини — це і , вони займають у списку відрізок з позицій і . Ребро виходить з цього піддерева, тому ребро мостом не є.

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

Дерево зберігаємо у вигляді списку суміжності . Листки в порядку обходу в глибину збираємо в масив .

vector<vector<int>> g;
vector<int> leaves;

Функція dfs виконує пошук у глибину з вершини . Параметр — батько вершини , для кореня p = -1. Спочатку обходимо всіх синів вершини .

void dfs(int v, int p = -1)
{
  for (int to : g[v])
    if (to != p) dfs(to, v);

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

  if (g[v].size() == 1)
    leaves.push_back(v);
}

Основна частина програми. Читаємо вхідні дані. Номер штабу на відповідь не впливає.

  scanf("%d %d", &n, &h);
  g.resize(n);
  for (i = 1; i < n; i++)
  {
    scanf("%d %d", &a, &b);
    g[a].push_back(b);
    g[b].push_back(a);
  }

Запускаємо пошук у глибину з вершини і збираємо листки. Змінна містить кількість листків, а r = \lceil m / 2 \rceil — кількість нових ходів.

  dfs(0);
  m = leaves.size();
  r = (m + 1) / 2;

Виводимо відповідь: листок з номером у списку з'єднуємо з листком з номером i + \lfloor m / 2 \rfloor.

  printf("%d\n", r);
  for (i = 0; i < r; i++)
    printf("%d %d\n", leaves[i], leaves[i + m / 2]);