Перейти до основного вмісту

Центроїдна декомпозиція

Необхідні попередні знання: Пошук у глибину (DFS), «Розділяй і володарюй», Дерева.

Вступ

Центроїдна декомпозиція — це техніка «розділяй і володарюй» на деревах. Її застосовують для розв'язання різноманітних задач, пов'язаних зі шляхами в дереві: підрахунку шляхів із певними властивостями, знаходження відстаней чи відповідей на запити щодо шляхів у дереві.

Ключова ідея полягає в тому, щоб рекурсивно декомпозувати дерево, знаходячи його центроїд. Ця особлива вершина при видаленні розбиває дерево на компоненти, кожна з яких містить щонайбільше половину вершин початкового дерева. Це гарантує логарифмічну глибину рекурсії, що приводить до ефективних алгоритмів.

Коли підходить цей алгоритм?
  • Задача стосується шляхів у дереві (підрахунок шляхів із заданою властивістю, відстані, запити «розділяй і володарюй» по всіх парах вершин)?
  • Дерево статичне за структурою (декомпозицію будують один раз)?
  • Запити стосуються шляхів між довільними парами вершин, а не агрегатів на одному фіксованому шляху? (якщо потрібні саме оновлення/запити на шляху aba \to bважко-легка декомпозиція)

Властивості та означення центроїда

Спершу розберемося, що таке центроїд. Центроїд дерева — це вершина, після видалення якої жодне піддерево не містить більш ніж N2\frac{N}{2} вершин, де NN — загальна кількість вершин у дереві.

Centroid Tree

Для будь-якого заданого дерева з NN вершин існує один або два центроїди. Якщо центроїдів два, то вони обов'язково суміжні.

Існування та єдиність

Теорема: Кожне дерево має щонайменше один центроїд і щонайбільше два центроїди. Якщо центроїдів два, то вони суміжні.

Доведення

Існування: Почнемо з будь-якої вершини й рухатимемося до того нащадка, чиє піддерево найбільше. Зупинимося, коли жоден нащадок не матиме більш ніж N2\frac{N}{2} вершин. У цей момент поточна вершина vv є центроїдом, тому що (1) жодне піддерево нащадка не містить більш ніж N2\frac{N}{2} вершин (за умовою зупинки) (2) «батьківська частина» (усі вершини, окрім піддерева vv, коли vv була нащадком) містить щонайбільше N2\frac{N}{2} вершин (інакше ми б не перейшли до vv від її батька).

Легко бачити, що цей процес завжди завершується, що й доводить існування щонайменше одного центроїда.

Єдиність: Припустимо, що є два центроїди uu і vv. Розглянемо шлях між ними. Коли ми видаляємо uu, вершина vv має лежати в компоненті з щонайбільше N2\frac{N}{2} вершинами. Аналогічно, коли ми видаляємо vv, вершина uu має лежати в компоненті з щонайбільше N2\frac{N}{2} вершинами. Це можливо лише тоді, коли uu і vv суміжні; інакше видалення будь-якої з них помістило б іншу в компоненту з більш ніж N2\frac{N}{2} вершинами. Це суперечить нашому початковому твердженню, що обидва центроїди лежать у компоненті з щонайбільше N2\frac{N}{2} вершинами. Більше того, якщо існують два центроїди, вони мають розбивати дерево на дві компоненти рівно по N2\frac{N}{2} вершин у кожній, що можливо лише тоді, коли NN парне.

Властивості та означення центроїдної декомпозиції

«Декомпозиція» дерева по суті означає рекурсивне знаходження центроїдів і розбиття дерева на піддерева відповідно до компонент центроїда. Така рекурсивна декомпозиція дерева на його компоненти породжує унікальний набір властивостей:

  1. Глибина декомпозиції: Глибина дорівнює O(logN)O(\log N), оскільки кожен рівень принаймні вдвічі зменшує розмір компоненти.
  2. Покриття шляхів: Кожен шлях у дереві проходить через центроїд якоїсь компоненти в декомпозиції.

Глибина декомпозиції

Теорема: Глибина, або кількість кроків, при застосуванні центроїдної декомпозиції до будь-якого дерева дорівнює O(logN)O(\log N).

Доведення

Розглянемо довільну вершину vv у початковому дереві. Простежимо, скільки разів vv може бути частиною компоненти протягом процесу декомпозиції.

На першому рівні vv перебуває в компоненті розміру NN. Коли ми видаляємо центроїд цієї компоненти, vv опиняється в компоненті розміру щонайбільше N2\frac{N}{2} (за властивістю збалансованості).

На другому рівні vv перебуває в компоненті розміру щонайбільше N2\frac{N}{2}. Видалення центроїда цієї компоненти поміщає vv у компоненту розміру щонайбільше N4\frac{N}{4}.

Продовжуючи за цією схемою, на kk-му рівні vv перебуває в компоненті розміру щонайбільше N2k1\frac{N}{2^{k-1}}.

Декомпозиція зупиняється, коли розміри компонент сягають 1. Це відбувається, коли N2k11\frac{N}{2^{k-1}} \leq 1, звідки klog2N+1k \leq \log_2 N + 1.

Отже, максимальна глибина дерева центроїдної декомпозиції дорівнює O(logN)O(\log N).

Наслідок: Оскільки кожна вершина бере участь щонайбільше в O(logN)O(\log N) рівнях декомпозиції, і ми обробляємо кожну вершину один раз на кожному рівні, алгоритми, що використовують центроїдну декомпозицію, зазвичай мають у часовій складності множник O(logN)O(\log N), помножений на обсяг роботи, виконуваної на одну вершину на одному рівні.

Покриття шляхів

Теорема: Кожен шлях у початковому дереві проходить через центроїд якоїсь компоненти в декомпозиції.

Доведення

Розглянемо довільний шлях PP від вершини uu до вершини vv у початковому дереві. Нам потрібно показати, що цей шлях проходить принаймні через один центроїд, обраний під час процесу декомпозиції.

Доведемо це індукцією за процесом декомпозиції.

База індукції: На першому рівні декомпозиції ми вибираємо центроїд c1c_1 всього дерева. Якщо шлях PP проходить через c1c_1, то все доведено.

Крок індукції: Припустимо, що шлях PP не проходить через c1c_1. Коли ми видаляємо c1c_1, дерево розпадається на кілька компонент. Оскільки PP — це зв'язний шлях, то обидві вершини uu та vv мають лежати в одній і тій самій компоненті CC після видалення c1c_1 (інакше PP мусив би проходити через c1c_1, щоб їх з'єднати, що суперечить нашому припущенню).

Тепер ми рекурсивно декомпозуємо компоненту CC. За припущенням індукції, застосованим до компоненти CC, шлях PP (який повністю міститься в CC) має проходити через центроїд якоїсь компоненти в декомпозиції CC.

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

Наслідок: Ця властивість є фундаментальною для коректності алгоритмів центроїдної декомпозиції. Вона гарантує, що коли ми обробляємо всі шляхи через кожен центроїд, ми покриваємо всі можливі шляхи в дереві рівно один раз на якомусь рівні декомпозиції. Саме тому центроїдна декомпозиція може ефективно розв'язувати задачі, пов'язані зі шляхами: кожен шлях розглядається рівно один раз — на тому рівні, де він уперше зустрічає центроїд.

Знаходження центроїда

Щоб ефективно знайти центроїд дерева:

  1. Обчислюємо розміри піддерев для всіх вершин за допомогою пошуку в глибину (DFS)
  2. Починаємо з будь-якої вершини
  3. Знаходимо нащадка vv, чиє піддерево містить більш ніж N2\frac{N}{2} вершин
  4. Переходимо до vv і повторюємо крок 3
  5. Якщо такого нащадка немає, то поточна вершина і є центроїдом

Часова складність: O(N)O(N).

Просторова складність: O(N)O(N).

Опис алгоритму

При використанні центроїдної декомпозиції загальний хід дій такий:

  1. Знаходимо центроїд поточного дерева/компоненти
  2. Обробляємо всі шляхи, що проходять через цей центроїд, і виконуємо будь-які потрібні обчислення
  3. Видаляємо центроїд (позначаємо його як використаний)
  4. Рекурсивно декомпозуємо кожне отримане піддерево

Це породжує дерево центроїдів. Кожен вузол цього дерева відповідає центроїду з якогось етапу декомпозиції. Це означає, що батьком центроїда (будь-якого заданого вузла) є той центроїд, який було знайдено в більшій компоненті, що його містила. Висота цього дерева дорівнює O(logN)O(\log N), як доведено раніше.

Centroid Tree

Наприклад, на зображенні вище зображено дерево центроїдів. Кожен вузол на кожному рівні дерева є центроїдом відповідної компоненти (наприклад, корінь — це центроїд усього дерева, найлівіший нащадок кореня — це центроїд найлівішого піддерева кореня тощо).

Реалізація

Ось реалізація центроїдної декомпозиції, що розв'язує конкретну задачу: підрахунок усіх шляхів у дереві з довжиною рівно KK.

У цій задачі нам задано дерево з NN вершин і потрібно підрахувати, скільки шляхів мають рівно KK ребер. Шлях визначається двома різними вершинами.

const int MAXN = 1e5;
vector<int> adj[MAXN];
bool removed[MAXN];
int subtree_size[MAXN];
int K; // Цільова довжина шляху
long long answer = 0; // Кількість шляхів довжини K

int get_subtree_size(int v, int p = -1) {
subtree_size[v] = 1;
for (int u : adj[v]) {
if (u == p || removed[u]) continue;
subtree_size[v] += get_subtree_size(u, v);
}
return subtree_size[v];
}

int get_centroid(int v, int tree_size, int p = -1) {
for (int u : adj[v]) {
if (u == p || removed[u]) continue;
if (subtree_size[u] * 2 > tree_size)
return get_centroid(u, tree_size, v);
}
return v;
}

void get_distances(int v, int p, int dist, vector<int>& distances) {
if (dist > K) return;
distances.push_back(dist);
for (int u : adj[v]) {
if (u == p || removed[u]) continue;
get_distances(u, v, dist + 1, distances);
}
}

void process_centroid(int centroid) {
unordered_map<int, int> all_distances;
all_distances[0] = 1;

for (int u : adj[centroid]) {
if (removed[u])
continue;

vector<int> current_distances;
get_distances(u, centroid, 1, current_distances);

for (int d : current_distances) {
if (K - d >= 0) {
answer += (all_distances[K - d] ? all_distances[K - d] : 0);
}
}

for (int d : current_distances) {
if (all_distances.find(d) == all_distances.end())
all_distances[d] = 0;
all_distances[d]++;
}
}
}

void decompose(int v) {
int tree_size = get_subtree_size(v);
int centroid = get_centroid(v, tree_size);

process_centroid(centroid);

removed[centroid] = true;

for (int u : adj[centroid]) {
if (!removed[u]) {
decompose(u);
}
}
}

Цей шаблон можна адаптувати для розв'язання різних задач за допомогою центроїдної декомпозиції. У цьому конкретному випадку він розв'язує задачу підрахунку всіх шляхів довжини KK. Стратегія така: для кожного центроїда підраховуємо шляхи, що проходять через нього, знаходячи пари вершин у різних піддеревах на відстанях d1d_1 і d2d_2, сума яких дорівнює KK (тобто шлях, що проходить через центроїд, складається з вершини в одному піддереві на відстані d1d_1 від центроїда та вершини в іншому піддереві на відстані d2d_2, де d1+d2=Kd_1 + d_2 = K). Для кожної відстані dd у поточному піддереві код підраховує, скільки вершин лежать на відстані KdK - d у попередніх піддеревах. Оптимізація пропускає відстані, більші за KK, щоб уникнути зайвої рекурсії.

Побудова дерева центроїдів

Якщо вам потрібно побудувати явну структуру дерева центроїдів (корисно для відповідей на запити):

int centroid_parent[MAXN];

int decompose(int v, int p = -1) {
int tree_size = get_subtree_size(v);
int centroid = get_centroid(v, tree_size);

centroid_parent[centroid] = p;
removed[centroid] = true;

for (int u : adj[centroid]) {
if (!removed[u]) {
decompose(u, centroid);
}
}

return centroid;
}

Задачі для практики

Відеоматеріали