Сховища та ходи утворюють дерево з вершин. Перекриття ходу – це видалення ребра. Потрібно додати найменшу кількість ребер так, щоб після видалення будь-якого одного ребра граф залишався зв'язним, тобто щоб у ньому не було мостів. Якщо граф залишається зв'язним, то з будь-якого сховища можна дістатися до штабу, тому номер штабу на відповідь не впливає.
Нижня оцінка. Назвемо листком вершину степеня , і нехай у дереві листків. Єдине ребро, що виходить з листка, є мостом. Щоб воно перестало бути мостом, до листка потрібно провести хоча б одне нове ребро. Кожне нове ребро має два кінці, тому покриває не більше двох листків. Отже, знадобиться не менше нових ребер.
Побудова. Покажемо, що ребер завжди достатньо. Запустимо пошук у глибину з будь-якої вершини і випишемо листки в тому порядку, в якому проходить їхній обхід: l_0, l_1, ..., l_{L-1}. Позначимо k = \lfloor L / 2 \rfloor і з'єднаємо листки, номери яких у цьому списку відрізняються на :
Отримується рівно ребер, і кожен листок виявляється кінцем хоча б одного з них. При непарному листок l_k бере участь у двох ребрах.
Приклад. На малюнку L = 5 і k = 2. Листки зафарбовані, поряд з кожним вказано його номер у порядку обходу. Додані ребра (3, 5), (4, 7) і показані пунктиром, листок бере участь у двох з них.
Чому мостів не залишається. Підвісимо дерево за вершину, з якої запускався пошук. Ребро дерева (, батько ) перестає бути мостом, якщо хоча б одне нове ребро з'єднує листок всередині піддерева з листком поза ним. Пошук у глибину обходить піддерево цілком, не перериваючись, тому листки цього піддерева йдуть у списку підряд і займають відрізок довжини . У піддереві є хоча б один листок, а всі листків у ньому опинитися не можуть: або у кореня є інші піддерева зі своїми листками, або корінь сам є листком. Тому 1 \le s \le L - 1.
Партнер кожного листка в новому ребрі відстоїть від нього в списку рівно на позицій. Розглянемо два випадки:
s \le k. Партнер першого листка відрізка знаходиться від нього на відстані k \ge s, тобто поза відрізком;
s > k. Поза відрізком лежить L - s \le L - k - 1 \le k листків. Візьмемо будь-який з них. Його партнер відстоїть від нього на позицій, а поза відрізком немає двох листків на такій відстані. Тому партнер лежить всередині відрізка.
В обох випадках знайдеться нове ребро, що з'єднує піддерево 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]);