Знайдіть центроїд#10158

Знайдіть центроїд (Розбір)

Нехай маємо дерево з вершин.

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

У задачі слід знайти всі центроїди дерева.

Розглянемо приклади дерев і їх центроїди (позначені зеленим кольором).

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

Якщо вершина має синів to_1, to_2, ..., to_k, то

sub_v = 1 + sub_{to_1} + sub_{to_2} + ... + sub_{to_k}

Вершина є центроїдом дерева тоді і тільки тоді, коли:

Наприклад, у правому на рисунку дереві з n = 5 вершинами центроїдом буде вершина , тому що

Приклад

Розглянемо дерево з прикладу. Вершина є центроїдом.

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

Граф будемо зберігати в списку суміжності .

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)