Разбор
Пусть имеется дерево из вершин.
Центроидом дерева называется такая вершина, при удалении которой дерево распадается на компоненты связности, количество вершин в каждой из которых не более .
В задаче следует найти все центроиды дерева.
Рассмотрим примеры деревьев и их центроиды (отмечены зеленым цветом).
Запустим поиск в глубину , который в вычислит количество вершин в поддереве с вершиной . Для приведенных примеров возле каждой вершины укажем соответствующее ей значение (поиск в глубину стартуем во всех примерах из вершины ).
Если вершина имеет сыновей , то
Вершина является центроидом дерева тогда и только тогда, когда:
для каждого ее сына имеет место ;
если из дерева удалить поддерево с корнем , то оно будет содержать не более вершин: ;
Например, в правом на рисунке дереве с вершинами центроидом будет вершина , потому что
;
;
;
Пример
Рассмотрим дерево из примера. Вершина является центроидом.
Реализация алгоритма
Граф будем хранить в списке смежности .
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) {
Установим , если вершина является центроидом.
int flag = 1;
Перебираем вершины , смежные с . Рассматриваем ребро .
for (int to : g[v]) if (to != p) {
Если для сына выполняется , то не является центроидом.
if (sub[to] > n / 2) flag = 0;
Если для сына выполняется , то в поддереве с корнем не содержится центроидов. Есть смысл продолжать поиск в сыне , если только .
if (sub[to] >= n / 2) centroid(to, 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]);
Реализация алгоритма — второе решение
Запустим поиск в глубину , который для каждой вершины вычислит следующие величины:
содержит количество вершин в поддереве с вершиной ;
содержит максимальный размер поддерева среди всех поддеревьев вершины , включая поддерево, содержащее родительскую вершину .
Тогда центроидом будет вершина с наименьшим значением . Таких вершин может быть либо одна либо две в дереве.
Вершина соединена с поддеревьями, размер которых соответственно равен и . Значение содержит максимальный размер поддерева.
В то же время наименьшим значением в массиве является . Следовательно вершина является центроидом.
Рассмотрим расстановку меток для дерева с двумя центроидами.
Две вершины имеют наименьшее значение в массиве : и .
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.
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
Перебираем вершины , смежные с . Рассматриваем ребро .
for to in g[v]: if to == p: continue
Если для сына выполняется , то не является центроидом.
if sub[to] > n // 2: flag = False
Если для сына выполняется , то в поддереве с корнем не содержится центроидов. Есть смысл продолжать поиск в сыне , если только .
if sub[to] >= n // 2: find_centroid(to, 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.
root = 0 dfs(1) print(root)