Нехай маємо дерево з вершин.
Центроїдом дерева називається така вершина, при видаленні якої дерево розпадається на компоненти зв'язності, кількість вершин у кожній з яких не більше .
У задачі слід знайти всі центроїди дерева.
Розглянемо приклади дерев і їх центроїди (позначені зеленим кольором).
Запустимо пошук в глибину , який у обчислить кількість вершин у піддереві з вершиною . Для наведених прикладів біля кожної вершини зазначимо відповідне їй значення (пошук в глибину стартуємо у всіх прикладах з вершини ).
Якщо вершина має синів to_1, to_2, ..., to_k, то
Вершина є центроїдом дерева тоді і тільки тоді, коли:
для кожного її сина має місце sub[to] \le n / 2;
якщо з дерева видалити піддерево з коренем , то воно буде містити не більше вершин: n - sub[v] \le n / 2;
Наприклад, у правому на рисунку дереві з n = 5 вершинами центроїдом буде вершина , тому що
sub[4] = 2 \le n / 2;
sub[3] = 1 \le n / 2;
n - sub[2] = 5 - 4 = 1 ≤ n / 2;
Приклад
Розглянемо дерево з прикладу. Вершина є центроїдом.
Реалізація алгоритму
Граф будемо зберігати в списку суміжності .
vector<vector<int> > g;Функція dfs повертає кількість вершин у піддереві з вершиною і зберігає це значення в .
int dfs(int v, int p = -1)
{
sub[v] = 1;
for (int to : g[v])
if (to != p) sub[v] += dfs(to, v);
return sub[v];
}Функція centroid здійснює пошук в глибину, знаходить і заносить центроїди в масив .
void centroid(int v, int p = -1)
{Встановимо flag = 1, якщо вершина є центроїдом.
int flag = 1;Перебираємо вершини , суміжні з . Розглядаємо ребро v → to.
for (int to : g[v])
if (to != p)
{Якщо для сина виконується sub[to] > n / 2, то не є центроїдом.
if (sub[to] > n / 2) flag = 0;Якщо для сина виконується sub[to] < n / 2, то в піддереві з коренем не міститься центроїдів. Є сенс продовжувати пошук у синові , якщо тільки sub[to] \ge n / 2.
if (sub[to] >= n / 2) centroid(to, v);
}Дерево без піддерева з коренем містить n - sub[v] вершин. Якщо воно містить більше вершин, то не центроїд.
if (n - sub[v] > n / 2) flag = 0;Якщо для вершини виконуються всі умови центроїда, то заносимо її в масив .
if (flag) centr.push_back(v);
}Основна частина програми. Ініціалізуємо масиви.
scanf("%d", &n);
g.resize(n + 1);
sub.resize(n + 1);Читаємо вхідний граф.
for (i = 0; i < n - 1; i++)
{
scanf("%d %d", &a, &b);
g[a].push_back(b);
g[b].push_back(a);
}Запускаємо пошук в глибину, шукаємо центроїди дерева.
dfs(1);
centroid(1);Виводимо один із центроїдів.
printf("%d\n", centr[0]);Реалізація алгоритму — друге рішення
Запустимо пошук в глибину , який для кожної вершини обчислить наступні величини:
містить кількість вершин у піддереві з вершиною ;
містить максимальний розмір піддерева серед усіх піддерев вершини , включаючи піддерево, що містить батьківську вершину .
Тоді центроїдом буде вершина з найменшим значенням . Таких вершин може бути або одна або дві в дереві.
Вершина v = 6 з'єднана з піддеревами, розміри яких відповідно рівні і . Значення f[6] = 5 містить максимальний розмір піддерева.
Водночас найменшим значенням у масиві є f[6] = 5. Отже, вершина є центроїдом.
Розглянемо розстановку міток для дерева з двома центроїдами.
Дві вершини мають найменше значення в масиві : f[1] = 3 і f[2] = 3.
void dfs(int v, int p = -1)
{
sub[v] = 1;
f[v] = 0;
for (int to : g[v])
if (to != p)
{
dfs(to, v);
sub[v] += sub[to];
f[v] = max(f[v], sub[to]);
}
f[v] = max(f[v], n - sub[v]);
if ((root == 0) || (f[v] < f[root])) root = v;
}Основна частина програми. Читаємо вхідні дані. Ініціалізуємо масиви.
scanf("%d", &n);
g.resize(n + 1);
f.resize(n + 1);
sub.resize(n + 1);
for (i = 0; i < n - 1; i++)
{
scanf("%d %d", &a, &b);
g[a].push_back(b);
g[b].push_back(a);
}Запускаємо пошук в глибину, виводимо один із центроїдів дерева .
root = 0;
dfs(1);
printf("%d\n", root);Python реалізація
Збільшимо розмір стека.
import sys
sys.setrecursionlimit(300000)Функція dfs повертає кількість вершин у піддереві з вершиною і зберігає це значення в .
def dfs(v, p = -1):
sub[v] = 1
for to in g[v]:
if to != p:
sub[v] += dfs(to, v)
return sub[v]Функція centroid здійснює пошук в глибину, знаходить і заносить центроїди в масив .
def find_centroid(v, p = -1):Встановимо flag = True, якщо вершина є центроїдом.
flag = TrueПеребираємо вершини , суміжні з . Розглядаємо ребро v → to.
for to in g[v]:
if to == p: continueЯкщо для сина виконується sub[to] > n / 2, то не є центроїдом.
if sub[to] > n // 2:
flag = FalseЯкщо для сина виконується sub[to] < n / 2, то в піддереві з коренем не міститься центроїдів. Є сенс продовжувати пошук у синові , якщо тільки sub[to] \ge n / 2.
if sub[to] >= n // 2:
find_centroid(to, v)Дерево без піддерева з коренем містить n - sub[v] вершин. Якщо воно містить більше вершин, то не центроїд.
if n - sub[v] > n // 2:
flag = FalseЯкщо для вершини виконуються всі умови центроїда, то заносимо її в масив .
if flag: centr.append(v)Основна частина програми. Читаємо вхідні дані. Ініціалізуємо масиви.
n = int(input())
g = [[] for _ in range(n + 1)]
sub = [0] * (n + 1)
centr = []Читаємо вхідний граф.
for i in range(n - 1):
a, b = map(int, input().split())
g[a].append(b)
g[b].append(a)Запускаємо пошук в глибину, шукаємо центроїди дерева.
dfs(1)
find_centroid(1)Виводимо один із центроїдів.
print(centr[0])Python реалізація — друге рішення
Збільшимо розмір стека.
import sys
sys.setrecursionlimit(200000)Запустимо пошук в глибину , який для кожної вершини обчислить наступні величини:
містить кількість вершин у піддереві з вершиною ;
містить максимальний розмір піддерева серед усіх піддерев вершини , включаючи піддерево, що містить батьківську вершину .
Тоді центроїдом буде вершина з найменшим значенням . Таких вершин може бути або одна або дві в дереві.
def dfs(v, p=-1):
global root
sub[v] = 1
f[v] = 0
for to in g[v]:
if to != p:
dfs(to, v)
sub[v] += sub[to]
f[v] = max(f[v], sub[to])
f[v] = max(f[v], n - sub[v])
if root == 0 or f[v] < f[root]:
root = vОсновна частина програми. Читаємо вхідні дані. Ініціалізуємо масиви.
n = int(input())
g = [[] for _ in range(n + 1)]
f = [0] * (n + 1)
sub = [0] * (n + 1)
for _ in range(n - 1):
a, b = map(int, input().split())
g[a].append(b)
g[b].append(a)Запускаємо пошук в глибину, виводимо один із центроїдів дерева .
root = 0
dfs(1)
print(root)