Дерева
Дерева є однією з ключових структур даних в алгоритмах і зустрічаються у широкому спектрі задач.
Дерево — це зв'язний, ациклічний, неорієнтований граф.
Граф називається деревом, якщо він задовольняє такі властивості:
Зв'язність — між будь-якою парою вершин у графі існує шлях.
Ациклічність — граф не містить циклів.
У дереві з вершинами завжди рівно ребро.
Основні властивості дерева
1. Зв'язність і унікальність шляхів
У дереві між будь-якою парою вершин існує єдиний шлях.
2. Кількість ребер
Для будь-якого дерева:
3. Відсутність циклів
Додавання навіть одного ребра до дерева завжди створює цикл.
4. Корінь (для кореневого дерева)
Якщо одна вершина обрана як корінь, дерево стає орієнтованим від кореня до його нащадків. Це використовується у рекурсивних обходах та алгоритмах (, та ін.).
5. Піддерева
Будь-яка вершина разом зі своїми нащадками утворює піддерево. Піддерево саме є деревом.
6. Листя
Вершини без нащадків (у кореневому дереві) називаються листям.
7. Глибина та висота
Глибина вершини — це відстань від кореня до цієї вершини.
Висота дерева — це максимальна глибина серед усіх вершин.
8. Центр дерева
Центр — це вершина (або пара суміжних вершин), яка мінімізує максимальну відстань до всіх інших вершин.
Альтернативні визначення дерева
Наступні визначення дерева є еквівалентними і можуть використовуватись у різних контекстах:
Граф без циклів, який стає незв'язним при видаленні будь-якого ребра.
Зв'язний граф з вершинами і ребрами.
Ациклічний граф з ребрами та однією зв'язною компонентою.
Представлення дерев у коді
Залежно від задачі і типу дерева (загальне, бінарне, кореневе і т.д.), використовуються різні структури даних:
1. Списки суміжності
Цей підхід часто використовується у задачах зі спортивного програмування та при роботі з неорієнтованими чи орієнтованими деревами.
int n; vector<vector<int>> tree(n); // дерево з n вершинами // додавання ребра між вершинами u та v tree[u].push_back(v); tree[v].push_back(u); // для неорієнтованого дерева
2. Масив батьків
Дуже компактне представлення, особливо корисне, коли дерево вже побудовано.
vector<int> parent(n); // parent[i] — батько вершини i // якщо i — корінь, parent[i] зазвичай встановлюється в -1 або 0
3. Вказівники / Структури (ООП стиль)
Зазвичай використовується для побудови бінарних дерев, тріє та інших структур, особливо в Python/Java/C++ з використанням об'єктно-орієнтованого програмування.
Бінарне дерево:
struct Node { int val; Node* left; Node* right; Node(int v) : val(v), left(nullptr), right(nullptr) {} };
Загальне дерево:
struct Node { int val; vector<Node*> children; Node(int v) : val(v) {} };
Граф з вершинами є деревом тоді і тільки тоді, коли:
Граф є зв'язним. Виконайте обхід у глибину (DFS) з першої вершини. Порахуйте кількість відвіданих вершин. Якщо вона дорівнює , то граф зв'язний.
. Якщо граф є деревом, він містить ребро.
Граф не містить циклів. Виконайте DFS з першої вершини. Якщо знайдеться зворотне ребро, граф містить цикл і не є деревом.
Достатньо перевірити умови і або і , щоб граф був деревом.
Приклад
Граф, наведений у прикладі, є деревом.
Оголосіть матрицю суміжності графа та масив .
#define MAX 110 int g[MAX][MAX], used[MAX];
Функція dfs виконує обхід у глибину, починаючи з вершини . Вершина є батьківською для вершини . Якщо буде виявлено цикл, змінна набуде значення .
void dfs(int v, int p) {
Якщо граф уже не є деревом , продовжувати пошук немає сенсу.
if (flag) return;
Позначимо вершину як відвідану.
used[v] = 1;
Вершина відвідана, збільшуємо значення на .
c++;
Ребро буде зворотним ребром та утворить цикл, якщо і вершина вже відвідана . Якщо цикл виявлено, встановлюємо . Якщо циклу немає, продовжуємо пошук з вершини .
for(int i = 0; i < n; i++) if ((i != p) && g[v][i]) if(used[i]) flag = 1; else dfs(i,v); }
Основна частина програми. Читаємо вхідні дані.
scanf("%d",&n); for(i = 0; i < n; i++) for(j = 0; j < n; j++) scanf("%d",&g[i][j]);
Підрахуємо кількість вершин, відвіданих під час обходу в глибину у змінній . Встановимо , якщо цикл відсутній у графі. Якщо цикл виявлено, набуде значення .
c = 0; flag = 0;
Спочатку всі вершини не відвідані (ініціалізуємо масив нулями).
memset(used,0,sizeof(used));
Починаємо обхід у глибину з вершини . Оскільки це корінь дерева, у неї немає батьківської вершини. Передаємо значення другим аргументом у функцію dfs.
dfs(0,-1);
Граф не є деревом, якщо є зворотне ребро або якщо граф не зв'язний .
if (flag || (c != n)) printf("NO\n"); else printf("YES\n");
Реалізація на Python — перевірка умов 1) та 3)
Функція dfs виконує обхід у глибину, починаючи з вершини . Вершина є батьківською для вершини . Якщо буде виявлено цикл, змінна набуде значення .
def dfs(v, p):
Оголошуємо глобальні змінні, які використовуються у функції.
global flag, c
Якщо граф уже не є деревом , продовжувати пошук немає сенсу.
if flag: return
Позначимо вершину як відвідану.
used[v] = 1
Вершина відвідана, збільшуємо значення на .
c += 1
Ребро буде зворотним ребром та утворить цикл, якщо та вершина вже відвідана . Якщо виявлено цикл, встановлюємо . Якщо циклу немає, продовжуємо пошук з вершини .
for i in range(n): if i != p and g[v][i]: if used[i]: flag = 1 else: dfs(i, v)
Основна частина програми. Читаємо вхідне значення .
n = int(input())
Підраховуємо кількість вершин, відвіданих під час обходу в глибину, у змінній . Встановлюємо , якщо циклів немає у графі. Якщо цикл виявлено, набуде значення .
c = 0 flag = 0
Спочатку всі вершини не відвідані (ініціалізуємо список нулями).
used = [0] * n
Створюємо матрицю суміжності , спочатку заповнену нулями.
g = [[0] * n for _ in range(n)]
Читаємо вхідну матрицю суміжності.
for i in range(n): g[i] = list(map(int, input().split()))
Запускаємо обхід у глибину з вершини . Оскільки це корінь дерева, вона не має батьківської вершини. Передаємо значення другим аргументом до функції dfs.
dfs(0, -1)
Граф не є деревом, якщо існує зворотне ребро або якщо граф не є зв'язним .
if flag or c != n: print("NO") else: print("YES")
Оголошуємо матрицю суміжності графа та масив .
#define MAX 101 int g[MAX][MAX], used[MAX];
Функція dfs реалізує обхід у глибину, починаючи з вершини .
void dfs(int v) {
Позначаємо вершину як відвідану.
used[v] = 1;
Вершина відвідана, збільшуємо значення на .
c++;
Перебираємо вершини , до яких можна перейти з вершини . Перехід з до можливий, якщо:
Існує ребро , тобто ;
Вершина ще не відвідана .
Якщо обидві умови виконуються, продовжуємо обхід у глибину з вершини .
for (int i = 1; i <= n; i++) if (g[v][i] && !used[i]) dfs(i); }
Основна частина програми. Читаємо вхідне значення .
scanf("%d", &n);
Підраховуємо кількість ребер у графі у змінній .
Edges = 0;
Спочатку всі вершини не відвідані (ініціалізуємо масив нулями).
memset(used, 0, sizeof(used));
Зчитуємо матрицю суміжності . Підраховуємо кількість ребер у графі.
for (i = 1; i <= n; i++) for (j = 1; j <= n; j++) { scanf("%d", &g[i][j]); Edges += g[i][j]; }
Оскільки граф неорієнтований, ребра і вважаються однаковими. Поділимо значення на .
Edges /= 2;
Порахуємо кількість відвіданих вершин під час обходу у глибину в змінній .
c = 0;
Запускаємо обхід у глибину з вершини .
dfs(1);
Граф є деревом, якщо кількість ребер у ньому дорівнює , а також якщо він зв'язний .
if ((Edges == n - 1) && (c == n)) printf("YES\n"); else printf("NO\n");
Реалізація на Python — перевірка умов 1) і 2)
Функція dfs реалізує обхід у глибину з вершини .
def dfs(v): global c
Позначаємо вершину як відвідану.
used[v] = 1
Вершина відвідана, збільшуємо значення на .
c += 1
Перебираємо вершини , до яких можна перейти з вершини . Перехід з до можливий, якщо:
Існує ребро , тобто ;
Вершина ще не відвідана .
Якщо обидві умови істинні, продовжуємо обхід у глибину з вершини .
for i in range(1, n + 1): if g[v][i] and not used[i]: dfs(i)
Основна частина програми. Зчитаємо значення .
n = int(input())
Підраховуємо кількість ребер у графі в змінній .
Edges = 0
Спочатку всі вершини не відвідані (ініціалізуємо масив нулями).
used = [0] * (n + 1)
Створюємо матрицю суміжності , початково заповнену нулями.
g = [[0] * (n + 1) for _ in range(n + 1)]
Зчитаємо матрицю суміжності . Підраховуємо кількість ребер у графі.
for i in range(1, n + 1): g[i] = [0] + list(map(int, input().split())) Edges += sum(g[i])
Оскільки граф неорієнтований, ребра та вважаються однаковими. Поділимо значення на .
Edges //= 2
Підраховуємо кількість відвіданих вершин під час обходу у глибину в змінній .
c = 0
Починаємо обхід у глибину з вершини .
dfs(1)
Граф є деревом, якщо кількість ребер у ньому дорівнює та якщо він зв'язний .
if Edges == n - 1 and c == n: print("YES") else: print("NO")
Дано зв'язний неорієнтований граф без петель та кратних ребер. Дозволяється видаляти ребра графа. Потрібно отримати дерево.
Вхідні дані. У першому рядку записано два цілих числа — кількість вершин та кількість ребер у графі. Далі записано пар цілих чисел, що задають ребра. Гарантується, що граф зв'язний.
Вихідні дані. Виведіть пару цілих чисел — ребра, які утворюють дерево. Ребра можна виводити у будь-якому порядку.

4 4 1 2 2 3 3 4 4 1
1 2 2 3 3 4
Виконаємо обхід у глибину (DFS), починаючи з першої вершини. Побудуємо дерево обходу в глибину та виведемо всі його ребра.
Приклад
Граф, наведений у прикладі, має наступну структуру:
Якщо запускати обхід у глибину з вершини , буде отримано наступний порядок ребер: .
Матриця суміжності графа зберігається у масиві .
#define MAX 101 int g[MAX][MAX], used[MAX];
Функція dfs виконує обхід у глибину, починаючи з вершини . Кожне ребро , що входить до дерева обходу в глибину, друкується.
void dfs(int v) { used[v] = 1; for (int i = 1; i <= n; i++) if (g[v][i] && !used[i]) { printf("%d %d\n",v,i); dfs(i); } }
Основна частина програми. Зчитуємо вхідні дані.
scanf("%d %d",&n,&m); memset(g,0,sizeof(g)); memset(used,0,sizeof(used)); while(m--) { scanf("%d %d",&a,&b); g[a][b] = g[b][a] = 1; }
Запускаємо обхід у глибину з першої вершини.
dfs(1);
Дано дерево — простий зв'язний граф без циклів.
Потрібно знайти максимальну кількість ребер, які можна видалити з дерева так, щоб отримати ліс, у якому кожна зв'язна компонента містить парну кількість вершин.
Наприклад, у дереві з вершинами, зображеному нижче, можна видалити не більше ніж ребро, щоб утворити парний ліс.

Вхідні дані. У першому рядку записано два цілих числа: кількість вершин — парне) та кількість ребер . У кожному з наступних рядків записано по два цілих числа — вершини, з'єднані ребром. Корінь дерева — вершина .
Вихідні дані. Виведіть максимальну кількість ребер, які можна видалити.

10 9 2 1 3 1 4 3 5 2 6 1 7 2 8 6 9 8 10 8
2
Задача полягає в тому, щоб розкласти дерево на зв'язні компоненти з парною кількістю вершин, максимізувавши кількість таких компонент.
Виконаємо обхід у глибину (DFS), починаючи з вершини , яка є коренем дерева. Для кожної вершини визначимо кількість вершин у піддереві з коренем . Якщо ця кількість парна, видалимо ребро, що з'єднує вершину з її батьківською вершиною у дереві DFS.
Для кореня (вершина ) розмір усього дерева парний. Але оскільки корінь не має батьківської вершини, ми повинні відняти від отриманого результату.
Приклад
Розглянемо дерево, наведене у прикладі. Біля кожної вершини вказано розмір її піддерева. Вершини , та мають піддерева з парною кількістю вершин. Оскільки вершина є коренем і не має ребра до батька, ми видаляємо два ребра: і .
Дерево було розкладено на зв'язні компоненти, кожна з яких містить парну кількість вершин.
Оголошуємо масиви.
#define MAX 101 int g[MAX][MAX], used[MAX];
Функція dfs виконує обхід у глибину, починаючи з вершини , і повертає кількість вершин у піддереві з коренем .
int dfs(int v) { used[v] = 1;
У змінній підраховуємо кількість вершин у піддереві з коренем .
int s = 1;
Перебираємо дочірні вершини вершини . Додаємо значення, що повертає виклик , до змінної .
for (int i = 1; i <= n; i++) if (g[v][i] && !used[i]) s += dfs(i);
Якщо розмір піддерева парний, видаляємо ребро між та її батьківською вершиною.
if (s % 2 == 0) res++;
Повертаємо кількість вершин у піддереві з коренем .
return s; }
Основна частина програми. Читаємо вхідні дані та будуємо граф.
scanf("%d %d", &n, &m); for (i = 0; i < m; i++) { scanf("%d %d", &a, &b); g[a][b] = g[b][a] = 1; }
Запускаємо обхід у глибину з вершини . У змінній підраховуємо кількість видалених ребер.
res = 0; dfs(1);
Оскільки немає ребра від вершини до її батьківської вершини, виводимо значення .
printf("%d\n", res - 1);
Реалізація на Python
Функція dfs виконує обхід у глибину, починаючи з вершини , і повертає кількість вершин у піддереві з коренем .
def dfs(v): used[v] = 1
У змінній обчислюємо кількість вершин у піддереві з коренем .
s = 1
Перебираємо дочірні вершини вершини . Додаємо значення виклику до змінної .
for i in range(1, n + 1): if g[v][i] and not used[i]: s += dfs(i)
Якщо розмір піддерева парний, видаляємо ребро між та її батьківською вершиною.
if s % 2 == 0: global res res += 1
Повертаємо кількість вершин у піддереві з коренем .
return s
Основна частина програми. Читаємо вхідні дані та будуємо граф.
n, m = map(int, input().split()) g = [[0] * (n + 1) for _ in range(n + 1)] used = [0] * (n + 1) for _ in range(m): a, b = map(int, input().split()) g[a][b] = g[b][a] = 1
Запускаємо обхід у глибину з вершини . У змінній підраховуємо кількість видалених ребер.
res = 0 dfs(1)
Оскільки немає ребра від вершини до її батьківської вершини, виводимо значення .
print(res - 1)
Дано структуру компанії. Потрібно для кожного працівника порахувати кількість його підлеглих.
Вхідні дані. У першому рядку знаходиться ціле число — кількість працівників. Працівники пронумеровані числами , , ..., , причому працівник номер є генеральним директором компанії.
Далі йдуть цілих чисел: для кожного працівника , , ..., вказано номер його безпосереднього керівника.
Вихідні дані. Виведіть цілих чисел: для кожного працівника , , ..., кількість його підлеглих.

5 1 1 2 3
4 1 1 0 0
Структура компанії — це дерево. Для кожної вершини цього дерева визначимо кількість вершин у піддереві з коренем у вершині . Кількість підлеглих для працівника дорівнюватиме (оскільки працівник не є своїм власним підлеглим).
Запускаємо обхід у глибину (DFS) з вершини , що відповідає генеральному директору компанії. Якщо — це дочірні вершини вершини , то:
Якщо вершина є листком, то .
Приклад
Розглянемо наведений приклад. Біля кожного працівника (вершини графа) вказано кількість його підлеглих.
Дерево зберігається у списку суміжності .
vector<vector<int> > g;
Функція dfs виконує обхід у глибину з вершини та повертає кількість вершин у піддереві з коренем . Вершина — це батьківська вершина для у дереві обходу в глибину.
int dfs(int v, int p) {
Спочатку піддерево з коренем містить лише саму вершину .
sum[v] = 1;
Розглянемо ребро . Якщо , додаємо до кількість вершин у піддереві з коренем у .
for (int to : g[v]) if (to != p) sum[v] += dfs(to, v);
Повертаємо кількість вершин у піддереві з коренем у вершині .
return sum[v]; }
Основна частина програми. Читаємо вхідні дані та будуємо дерево.
scanf("%d", &n); g.resize(n + 1); for (i = 2; i <= n; i++) { scanf("%d", &x);
Для працівника його безпосереднім керівником у компанії є працівник .
g[i].push_back(x); g[x].push_back(i); }
Запускаємо обхід у глибину з вершини .
sum.resize(n + 1); dfs(1, -1);
Виводимо відповідь. Кількість підлеглих працівника дорівнює .
for (i = 1; i <= n; i++) printf("%d ", sum[i] - 1); printf("\n");
Реалізація на Java
import java.util.*; public class Main { static ArrayList<Integer>[] g; static int sum[]; static int n; static int dfs(int v, int p) { sum[v] = 1; for(int to : g[v]) if (to != p) sum[v] += dfs(to,v); return sum[v]; } @SuppressWarnings("unchecked") public static void main(String[] args) { Scanner con = new Scanner(System.in); n = con.nextInt(); g = new ArrayList[n+1]; // unchecked sum = new int[n+1]; for (int i = 0; i <= n; i++) g[i] = new ArrayList<Integer>(); for (int i = 2; i <= n; i++) { int x = con.nextInt(); g[i].add(x); g[x].add(i); } dfs(1,-1); for (int i = 1; i <= n; i++) System.out.print(sum[i] - 1 + " "); System.out.println(); con.close(); } }
Реалізація на Python
Збільшуємо розмір стеку для рекурсії.
import sys sys.setrecursionlimit(1000000)
Функція dfs виконує обхід у глибину, починаючи з вершини , і повертає кількість вершин у піддереві з коренем . Вершина — це батьківська вершина для в обході у глибину.
def dfs(v, p):
Спочатку піддерево з коренем містить лише саму вершину .
sum[v] = 1
Розглянемо ребро . Якщо , додаємо до кількість вершин у піддереві з коренем у вершині .
for to in g[v]: if to != p: sum[v] += dfs(to,v)
Повертаємо кількість вершин у піддереві з коренем у вершині .
return sum[v]
Основна частина програми. Зчитуємо кількість працівників.
n = int(input())
Ініціалізуємо список суміжності графа .
g = [[] for _ in range(n+1)]
Читаємо вхідні дані та будуємо дерево.
lst = list(map(int, input().split())) for i in range(2,n+1): a = lst[i-2]
Для працівника безпосереднім керівником у компанії є працівник .
g[a].append(i) g[i].append(a)
Ініціалізуємо список .
sum = [0] * (n + 1)
Запускаємо обхід у глибину з вершини .
dfs(1,-1)
Виводимо відповідь. Кількість підлеглих працівника дорівнює .
for i in range(1, n+1): print(sum[i] - 1,end=" ")
Дано дерево, що складається з вершин.
Діаметром дерева називається максимальна відстань між двома вершинами. Знайдіть діаметр заданого дерева.
Вхідні дані. У першому рядку знаходиться ціле число — кількість вершин у дереві. Вершини пронумеровані від до .
Кожен з наступних рядків описує ребро і містить два числа та , що означає, що між вершинами та є ребро.
Вихідні дані. Виведіть одне ціле число — діаметр дерева.

5 1 2 1 3 3 4 3 5
3
Виконаємо обхід у глибину з будь-якої вершини, наприклад, з вершини . Знаходимо вершину, найвіддаленішу від вершини , позначимо її як вершину . Потім запускаємо обхід у глибину з вершини та знаходимо вершину, найвіддаленішу від , позначимо її як вершину . Відстань між вершинами та буде максимальною і дорівнюватиме діаметру дерева.
Приклад
Розглянемо дерево з прикладу.
Діаметр цього дерева дорівнює . Максимальна відстань досягається, наприклад, між вершинами і .
Задача на тренування
Знайдіть діаметр дерева.
Зберігаємо задане дерево у списку суміжності . Найкоротші відстані від початкової вершини до кожної вершини зберігаємо у масиві .
vector<vector<int>> g; vector<int> dist;
Функція dfs виконує обхід у глибину, починаючи з вершини . Найкоротша відстань від початкової вершини до вершини дорівнює . Батьківська вершина вершини в обході в глибину — це .
void dfs(int v, int d, int p = -1) {
Зберігаємо найкоротшу відстань від початкової вершини до вершини у масиві .
dist[v] = d;
Перебираємо всі ребра . Для кожного сина вершини (де не є батьківською вершиною ) запускаємо обхід у глибину. Відстань від початкової вершини до вершини буде .
for (int to : g[v]) if (to != p) dfs(to, d + 1, v); }
Основна частина програми. Читаємо вхідні дані.
scanf("%d", &n); g.resize(n + 1); dist.resize(n + 1);
Будуємо неорієнтоване дерево.
for (i = 1; i < n; i++) { scanf("%d %d", &u, &v); g[u].push_back(v); g[v].push_back(u); }
Знаходимо найкоротші відстані від вершини до всіх інших вершин. Вершина, найдальша від неї, позначається як .
dfs(1, 0, -1); a = max_element(dist.begin(), dist.begin() + n + 1) – dist.begin();
Знаходимо найкоротші відстані від вершини до всіх інших вершин. Вершина, найдальша від неї, позначається як .
dfs(a, 0, -1); b = max_element(dist.begin(), dist.begin() + n + 1) – dist.begin();
Виводимо діаметр дерева — найкоротшу відстань між вершинами та .
printf("%d\n", dist[b]);
Дано дерево, що складається з вершин.
Для кожної вершини знайдіть максимальну відстань до будь-якої іншої вершини.
Вхідні дані. У першому рядку міститься ціле число — кількість вершин у дереві. Вершини пронумеровані від до .
Наступні рядків описують ребра: кожен рядок містить два числа та , що означає наявність ребра між вершинами та .
Вихідні дані. Виведіть цілих чисел. Для кожної вершини від до виведіть максимальну відстань до будь-якої іншої вершини дерева.

5 1 2 1 3 3 4 3 5
2 3 2 3 3
За допомогою двох обходів у глибину (DFS) знаходимо діаметр дерева.
Оскільки дерево — це зв'язний граф без циклів, то як DFS, так і BFS коректно знаходять найкоротші відстані від початкової вершини до всіх інших.
Нехай та — дві вершини, відстань між якими є максимальною, тобто визначають діаметр дерева.
Обчислюємо найкоротші відстані від вершини до всіх інших вершин та зберігаємо їх у масиві , а від вершини — у масиві .
Тоді для кожної вершини найбільша відстань до будь-якої іншої вершини дорівнює:
Приклад
Розглянемо дерево з прикладу.
Діаметром цього дерева є, наприклад, шлях між вершинами та .
Вхідне дерево зберігається у списку суміжності .
Найкоротші відстані від вершин та зберігаються відповідно у масивах та .
vector<vector<int>> g; vector<int> dista, distb;
Функція dfs виконує обхід у глибину, починаючи з вершини .
Параметр — найкоротша відстань від початкової вершини до вершини .
Результати зберігаються в масиві .
Параметр — батьківська вершина вершини у дереві обходу.
void dfs(int v, int d, vector<int>& dist, int p = -1) {
Зберігаємо найкоротшу відстань від початкової вершини до вершини в масиві .
dist[v] = d;
Перебираємо всі ребра . Для кожного сина вершини (тобто такого , що не є батьківською вершиною ) виконуємо обхід у глибину.
for (int to : g[v]) if (to != p) dfs(to, d + 1, dist, v); }
Основна частина програми. Читаємо вхідні дані.
scanf("%d", &n); g.resize(n + 1); dista.resize(n + 1); distb.resize(n + 1);
Будуємо неорієнтоване дерево.
for (i = 1; i < n; i++) { scanf("%d %d", &u, &v); g[u].push_back(v); g[v].push_back(u); }
Обчислюємо найкоротші відстані від вершини . Вершина, найдальша від неї, позначається як .
dfs(1, 0, dista, -1); a = max_element(dista.begin() + 1, dista.begin() + n + 1) - dista.begin();
Далі обчислимо найкоротші відстані від вершини та збережемо їх у масиві . Вершина, яка знаходиться найдалі від , виявиться вершиною . Відстань між вершинами та — це діаметр дерева.
dfs(a, 0, dista, -1); b = max_element(dista.begin() + 1, dista.begin() + n + 1) - dista.begin();
Далі обчислюємо найкоротші відстані від вершини і зберігаємо їх у масиві .
dfs(b, 0, distb, -1);
Для кожної вершини виводимо відстань до найвіддаленішої від неї вершини.
for (i = 1; i <= n; i++) printf("%d ", max(dista[i], distb[i])); printf("\n");
Реалізація на Python
Збільшуємо стек для рекурсії.
import sys sys.setrecursionlimit(1000000)
Функція dfs виконує обхід у глибину з вершини .
Параметр — найкоротша відстань від джерела до вершини .
Результати зберігаються в масиві .
Параметр — батьківська вершина в обході.
def dfs(v, d, dist, p = -1):
Зберігаємо найкоротшу відстань до вершини в .
dist[v] = d
Перебираємо всі вершини , суміжні з , і виконуємо обхід у глибину, якщо не є батьком .
for to in g[v]: if to != p: dfs(to, d + 1, dist, v)
Основна частина програми. Зчитуємо вхідні дані.
n = int(input()) g = [[] for _ in range(n + 1)] dista = [0] * (n + 1) distb = [0] * (n + 1)
Будуємо неорієнтоване дерево.
for _ in range(n - 1): u, v = map(int, input().split()) g[u].append(v) g[v].append(u)
Обчислимо найкоротші відстані від вершини . Найвіддаленішою від неї вершиною є вершина .
dfs(1, 0, dista) a = dista.index(max(dista[1:]))
Далі обчислимо найкоротші відстані від вершини і збережемо їх у масиві . Найвіддаленішою від виявляється вершина . Відстань між вершинами та є діаметром дерева.
dfs(a, 0, dista) b = dista.index(max(dista[1:]))
Далі обчислюємо найкоротші відстані від вершини і зберігаємо їх у масиві .
dfs(b, 0, distb)
Для кожної вершини виводимо відстань до найвіддаленішої від неї вершини.
result = [max(dista[i], distb[i]) for i in range(1, n + 1)] print(*result)
Батьки Романа подарували йому неорієнтований, зв’язний, зважений граф з вершинами та ребром. Роман хоче знайти загальну довжину всіх шляхів між кожною парою вершин у графі. Довжина шляху визначається як сума ваг ребер, що входять до нього. Оскільки шлях від вершини до вершини такий самий, як і від до , Роман вважає їх одним і тим самим шляхом.
Вхідні дані. У першому рядку задано ціле число — кількість вершин у графі.
Кожен з наступних рядків описує ребро і містить три цілі числа: номери двох вершин, з'єднаних ребром (вершини пронумеровані від до ), та вагу ребра.
Вихідні дані. Виведіть загальну довжину всіх різних шляхів між усіма парами вершин, за модулем .

3 1 2 1 1 3 3
8
6 1 2 5 1 3 1 2 4 2 2 5 4 2 6 3
90
Виконай пошук у глибину, починаючи з довільної вершини дерева. Розглянемо ребро з вагою . Нехай кількість вершин у піддереві з коренем у вершині дорівнює . Тоді по один бік ребра є вершин, а по інший — . Ребро входить у різних шляхів між парами вершин. Таким чином, його внесок у загальну довжину всіх шляхів становить . Потрібно лише пройтися всіма ребрами за допомогою пошуку в глибину і підсумувати їхній внесок у загальну довжину всіх шляхів.
Приклад
Графи, наведені у прикладах, мають таку структуру:
Розглянемо внесок ребер у загальну довжину всіх шляхів у першому прикладі.
Ребро з вагою входить до двох шляхів:
;
;
Його внесок у загальну суму: .
Ребро з вагою входить до двох шляхів:
;
;
Його внесок у загальну суму: .
Загальна довжина всіх шляхів дорівнює .
У другому прикладі розглянемо внесок ребра з вагою у загальну довжину всіх шляхів.
Кількість вершин у піддереві з коренем у вершині дорівнює (включаючи саму вершину ). Тоді з іншого боку ребра знаходиться вершини. Отже, кількість різних шляхів, що проходять через ребро , дорівнює .
Справді, це всі пари вершин, у яких одна належить піддереву вершини , а інша — ні:
Внесок ребра з вагою у загальну довжину всіх шляхів дорівнює:
Задаємо значення модуля як .
#define MOD 1000000000
Вхідний зважений граф зберігається у вигляді списку суміжності .
vector<vector<pair<int,int> > > g;
Функція dfs виконує обхід у глибину, починаючи з вершини , і повертає кількість вершин у піддереві з коренем у (включаючи саму вершину ). Кількість таких вершин підраховується у змінній . Спочатку встановлюємо , оскільки піддерево вже включає вершину .
int dfs(int v, int p = -1) { int cnt = 1; for (auto edge : g[v]) { int to = edge.first; int w = edge.second;
Розглянемо ребро з вагою . Нехай — кількість вершин у піддереві з коренем у вершині . Тоді з одного боку ребра знаходиться вершин, а з іншого — . Ребро входить у різних шляхів між парами вершин. Таким чином, внесок ребра у загальну довжину всіх шляхів дорівнює:
if (to != p) { int c = dfs(to, v); res = (res + 1LL * c * (n - c) * w) % MOD; cnt += c; } } return cnt; }
Основна частина програми. Зчитування вхідного зваженого графа у список суміжності .
scanf("%d", &n); g.resize(n + 1); for (i = 1; i < n; i++) { scanf("%d %d %d", &u, &v, &d); g[u].push_back({v, d}); g[v].push_back({u, d}); }
Запускаємо обхід у глибину з вершини . Вершини графа нумеруються від до .
dfs(1);
Виводимо відповідь.
printf("%lld\n", res);
Реалізація на Java
import java.util.*; import java.io.*; class Edge { int to; int weight; public Edge(int to, int weight) { this.to = to; this.weight = weight; } } public class Main { static int MOD = 1000000000; static ArrayList<Edge>[] g; static int n, m; static long res; static int dfs(int v, int p) { int cnt = 1; for(Edge edge : g[v]) { int to = edge.to; int w = edge.weight; if (to != p) { int c = dfs(to, v); res = (res + 1L * c * (n - c) * w) % MOD; cnt += c; } } return cnt; } public static void main(String[] args) { FastScanner con = new FastScanner(System.in); n = con.nextInt(); g = new ArrayList[n+1]; // unchecked for (int i = 0; i <= n; i++) g[i] = new ArrayList<Edge>(); for (int i = 1; i < n; i++) { int u = con.nextInt(); int v = con.nextInt(); int d = con.nextInt(); g[u].add(new Edge(v,d)); g[v].add(new Edge(u,d)); } dfs(1,-1); System.out.println(res); } } class FastScanner { BufferedReader br; StringTokenizer st; public FastScanner(InputStream inputStream) { br = new BufferedReader(new InputStreamReader(inputStream)); st = new StringTokenizer(""); } public String next() { while (!st.hasMoreTokens()) { try { st = new StringTokenizer(br.readLine()); } catch (Exception e) { return null; } } return st.nextToken(); } public int nextInt() { return Integer.parseInt(next()); } }
Реалізація на Python
Оголосимо значення модуля як .
MOD = 1000000000
Функція dfs виконує обхід графа в глибину, починаючи з вершини , і повертає кількість вершин у піддереві з коренем у (включаючи ). Кількість таких вершин підраховується у змінній . Спочатку , оскільки піддерево вже містить вершину .
def dfs(v, p = -1): global res cnt = 1 for to, w in g[v]:
Розглянемо ребро з вагою . Нехай — кількість вершин у піддереві з коренем у вершині . Тоді одна сторона ребра містить вершин, а інша — вершин. Ребро входить у різних шляхів між парами вершин. Таким чином, внесок ребра у загальну довжину всіх шляхів дорівнює
if to != p: c = dfs(to, v) res = (res + c * (n - c) * w) % MOD cnt += c return cnt
Основна частина програми. Зчитування вхідних даних.
n = int(input()) g = [[] for _ in range(n + 1)] res = 0 for _ in range(n - 1): u, v, d = map(int, input().split()) g[u].append((v, d)) g[v].append((u, d))
Запуск обходу в глибину з вершини . Вершини графа пронумеровані від до .
dfs(1)
Виведення відповіді.
print(res)
Задано кореневе дерево, що складається з вершин. Кожна вершина пофарбована в один із кольорів. Для кожної вершини визначте кількість різних кольорів, що зустрічаються в піддереві з коренем у .
Вхідні дані. У першому рядку записано одне ціле число . Наступні рядків описують вершини дерева. -ий рядок містить два цілих числа та , де — батько вершини , а — колір вершини . Для кореня дерева .
Вихідні дані. Виведіть цілих чисел — по одному для кожної вершини від до . Для кожної вершини виведіть кількість різних кольорів у піддереві з коренем у цій вершині.

5 2 1 3 2 0 3 3 3 2 1
1 2 3 1 1
Виконаємо обхід дерева в глибину, починаючи з кореня. Для кожної вершини будемо підтримувати множину , яка накопичує всі кольори вершин у піддереві з коренем у .
Якщо — нащадок вершини під час обходу, тоді множина повинна бути об'єднана з .
Кількість різних кольорів у піддереві з коренем у вершині дорівнює кількості елементів у множині .
Приклад
Зліва вказано колір кожної вершини. Справа — множина кольорів у її піддереві.
Масив зберігає колір вершини . Множина буде накопичувати кольори, що зустрічаються в піддереві з коренем у вершині .
Орієнтоване дерево представлено у вигляді списку суміжності . Масив зберігає кількість різних кольорів у піддереві з коренем у вершині .
#define MAX 1000010 int color[MAX], res[MAX]; set<int> s[MAX]; vector<vector<int> > g;
Функція dfs виконує обхід дерева в глибину, починаючи з вершини .
void dfs(int v) {
Спочатку до множини додається колір самої вершини .
s[v].insert(color[v]);
Ітеруємось по ребрах дерева .
for (int to : g[v]) { dfs(to);
Для кожного ребра об'єднуємо множину у . Якщо розмір менший за розмір , спочатку міняємо їх місцями. Потім додаємо вміст меншої множини до більшої .
if (s[v].size() < s[to].size()) s[v].swap(s[to]); s[v].insert(s[to].begin(), s[to].end());
Очищаємо множину — вона більше не потрібна.
s[to].clear(); }
Після цього зберігаємо кількість різних кольорів у піддереві в , тобто розмір множини .
res[v] = s[v].size(); }
Основна частина програми. Зчитування вхідних даних.
scanf("%d",&n); g.resize(n+1); for(i = 1; i <= n; i++) { scanf("%d %d",&p,&c); g[p].push_back(i); color[i] = c; }
Почніть обхід у глибину з кореня дерева — вершини з індексом .
dfs(0);
Виведіть відповідь.
for(i = 1; i <= n; i++) printf("%d ",res[i]); printf("\n");
Після довгого і напруженого робочого тижня жителі Манчестера і Ліверпуля вирішили на вихідних вирушити в похід. Під час прогулянки лісом вони натрапили на унікальне дерево з вершинами. Вершини дерева пронумеровані від до , і кожна з них пофарбована в один з можливих кольорів.
Щоб не нудьгувати, вони вирішили перевірити свою логіку. Коренем дерева є вершина . Для кожної вершини учасники вирішили знайти її найближчого предка такого ж кольору.
Вхідні дані. Перша строка містить два цілі числа і — кількість вершин у дереві та кількість можливих кольорів.
Друга строка містить ціле число: -те з них вказує батька вершини .
Третя строка містить цілих чисел — кольори вершин. Кожен колір — це ціле число від до включно.
Вихідні дані. Виведіть цілих чисел в одному рядку: для кожної вершини виведіть номер її найближчого предка з таким самим кольором. Якщо такого предка не існує, виведіть .
5 4 1 1 3 3 1 4 2 1 2
-1 -1 -1 1 3
Розглянемо спрощену версію задачі. Нехай усі вершини дерева пофарбовані в один і той самий колір. Ініціалізуємо порожній стек і виконуємо обхід у глибину з кореня дерева. При вході у вершину додаємо її до стеку. Коли обробка вершини завершується, видаляємо з вершини стеку елемент (ним буде ). Після завершення DFS стек знову буде порожнім.
Питання: Що буде на вершині стеку в момент входу у вершину , безпосередньо перед її додаванням?
Тепер перейдемо до розв'язку оригінальної задачі. Створимо стеків — по одному для кожного кольору (наприклад, у вигляді вектора стеків). Спочатку всі стеки порожні. Виконуємо обхід у глибину з кореня дерева — вершини . Обробка вершини , пофарбованої у колір , включає наступні кроки:
Якщо стек не порожній, його верхній елемент містить номер вершини, яка є найближчим предком вершини того ж кольору. Якщо стек порожній, такої вершини не існує, і відповідь для дорівнює .
Додаємо вершину до стеку .
Рекурсивно виконуємо DFS для всіх дітей вершини .
Після завершення обробки вершини , видаляємо її зі стеку .
Під час обходу в глибину, коли ми перебуваємо у вершині , стеки містять інформацію про кольори всіх вершин на унікальному шляху від кореня до вершини . Зокрема, стек зберігає номери всіх вершин на цьому шляху, пофарбованих у колір . Ці вершини додаються до стеку в порядку відвідування DFS.
Приклад
Коли обхід у глибину досягає вершини , два з стеків будуть порожні (ті, що відповідають кольорам і ).
Стек номер (для кольору ) міститиме вершину , а стек номер (для кольору ) — вершину . Оскільки вершина має колір , її найближчий предок того ж кольору — це вершина, що знаходиться на вершині стеку номер .
Отже, найближчий предок вершини того ж кольору — це вершина .
Оголошення масивів.
vector<int> col, res; vector<vector<int> > g; vector<stack<int> > s;
Функція dfs виконує обхід у глибину, починаючи з вершини .
void dfs(int v) {
Вершина має колір .
int color = col[v];
Номер найближчого предка вершини того ж кольору зберігається у .
if(s[color].empty()) res[v] = -1; else res[v] = s[color].top();
Додаємо вершину до стеку, що відповідає її кольору.
s[color].push(v);
Проходимо всіх нащадків вершини і рекурсивно викликаємо обхід у глибину для кожного з них.
for (int to : g[v]) dfs(to);
Після завершення обробки вершини (тобто при виході з неї) видаляємо її зі стеку, що відповідає її кольору.
s[color].pop(); }
Основна частина програми. Зчитування вхідних даних. Побудова дерева.
scanf("%d %d", &n, &c); g.resize(n + 1); for(i = 2; i <= n; i++) { scanf("%d",&val); g[val].push_back(i); }
Зчитуємо кольори вершин дерева.
col.resize(n + 1); for(i = 1; i <= n; i++) scanf("%d",&col[i]);
Запускаємо обхід у глибину з вершини .
s.resize(c + 1); res.resize(n + 1); dfs(1);
Виводимо відповідь.
for(i = 1; i <= n; i++) printf("%d ",res[i]); printf("\n");
Реалізація на Python
Збільшуємо розмір стеку рекурсії.
import sys sys.setrecursionlimit(1000000)
Функція dfs виконує обхід у глибину, починаючи з вершини .
def dfs(v):
Вершина має колір .
color = col[v]
Номер найближчого предка вершини того ж кольору зберігається у .
if not s[color]: res[v] = -1 else: res[v] = s[color][-1]
Додаємо вершину до стеку, що відповідає її кольору.
s[color].append(v)
Ітеруємо по всіх нащадках вершини та рекурсивно виконуємо обхід у глибину для кожного з них.
for to in g[v]: dfs(to)
Після завершення обробки вершини (тобто після виходу з неї) видаляємо її зі стеку відповідного кольору.
s[color].pop()
Основна частина програми. Зчитуємо вхідні дані. Будуємо дерево.
n, c = map(int, input().split()) lst = list(map(int, input().split())) g = [[] for _ in range(n + 1)] for i in range(2, n + 1): val = lst[i - 2] g[val].append(i)
Зчитуємо кольори вершин дерева.
col = [0] * (n + 1) col[1:] = list(map(int, input().split()))
Запускаємо обхід у глибину з вершини .
s = [[] for _ in range(c + 1)] res = [0] * (n + 1) dfs(1)
Виводимо відповідь.
print(*res[1:])
У вільний час фермер Джон створив власну платформу для поширення відео під назвою MooTube. На MooTube його корови можуть записувати, публікувати та переглядати багато смішних відео. На цей момент корови завантажили відео, пронумерованих від до .
Проте фермер Джон не до кінця розуміє, як допомогти своїм коровам знаходити нові відео, які їм можуть сподобатися. Він хоче реалізувати систему рекомендацій для кожного відео — список «рекомендованих відео» на основі їхньої схожості з уже переглянутими.
Щоб визначити, наскільки два відео схожі, Джон вводить метрику релевантності. Він вручну оцінює пар відео та призначає кожній парі певну оцінку релевантності. Використовуючи ці пари, Джон будує мережу, де кожне відео — це вершина в графі, а обрані пари з'єднані ребрами з відповідними значеннями релевантності.
Для зручності Джон вибирає саме пар так, щоб між будь-якими двома відео існував рівно один шлях — іншими словами, мережа відео утворює дерево.
Він вирішує, що релевантність між двома відео має визначатися як мінімальне значення релевантності серед усіх ребер на шляху, що з'єднує ці два відео.
Тепер фермер Джон хоче вибрати значення так, щоб для будь-якого відео на MooTube всі інші відео з релевантністю принаймні до нього відображалися як рекомендації. Однак його турбує, що надто багато рекомендацій можуть відволікати корів від виробництва молока, тому він хоче точно оцінити, скільки відео буде рекомендовано для різних значень .
Фермер Джон звертається до вас по допомогу: вам задано кілька запитів, кожен з яких містить значення та номер відео. Для кожного запиту потрібно визначити, скільки інших відео буде рекомендовано, якщо мінімальна необхідна релевантність дорівнює .
Вхідні дані. Перша стрічка містить два цілі числа та — кількість відео та кількість запитів відповідно.
Наступні стрічок описують пари відео, релевантність яких була вручну оцінена фермером Джоном. Кожна з цих стрічок містить три цілі числа та , що означає, що відео та з'єднані ребром із релевантністю .
Далі йдуть стрічок, кожна з яких описує один із запитів фермера Джона. -та стрічка містить два цілі числа та — це означає, що в -му запиті фермер Джон хоче дізнатись, скільки відео буде рекомендовано для відео , якщо мінімально допустима релевантність для рекомендацій дорівнює .
Вихідні дані. Виведіть стрічок. У -тій стрічці виведіть відповідь на -тий запит фермера Джона.

Приклад. Фермер Джон встановив наступні зв'язки між відео:
Відео і відео мають релевантність ,
Відео і відео мають релевантність ,
Відео і відео мають релевантність .
На основі цих даних можемо обчислити релевантність між іншими парами відео:
Відео і відео : релевантність = ,
Відео і відео : релевантність = ,
Відео і відео : релевантність = .
Розглянемо, які відео будуть рекомендовані для наступних запитів:
Для відео при , будуть рекомендовані відео , та .
Для відео при , будуть рекомендовані відео та .
Для відео при , жодне відео не буде рекомендоване.
4 3 1 2 3 2 3 2 2 4 4 1 2 4 1 3 1
3 0 2
Для кожного запиту, заданого парою , виконаємо обхід у глибину або в ширину (DFS або BFS), починаючи з вершини . Під час обходу переходимо лише по тих ребрах, релевантність яких не менша за .
Порахуємо кількість досяжних вершин , не враховуючи стартову вершину . Значення — це відповідь на запит .
Приклад
Фермер Джон встановив наступні зв'язки між відео:
Відео і відео мають релевантність ,
Відео і відео мають релевантність ,
Відео і відео мають релевантність .
На основі цих даних можемо обчислити релевантність між іншими парами відео:
Відео і відео : релевантність = ,
Відео і відео : релевантність = ,
Відео і відео : релевантність = .
Тепер подивимося, які відео будуть рекомендовані для наступних запитів:
З відео при будуть рекомендовані відео , та .
З відео при будуть рекомендовані відео та .
З відео при жодне відео не буде рекомендоване.
Збережемо вхідний граф у вигляді списку суміжності .
vector<vector<pair<int, int>>> g;
Функція bfs виконує обхід у ширину, починаючи з вершини , і повертає кількість відвіданих вершин, не враховуючи саму вершину . Під час обходу переходимо лише по ребрах, вага яких не менша за задане значення .
int bfs(int start, int k) {
Масив найкоротших відстаней у цій задачі не потрібен — достатньо лише відстежувати, чи була вершина відвідана. Якщо вершина відвідана, встановлюємо .
vector<int> used(n + 1); used[start] = 1;
Ініціалізуємо чергу та додаємо в неї початкову вершину .
queue<int> q; q.push(start);
Кількість відвіданих вершин під час обходу в ширину (без урахування початкової вершини ) рахуємо у змінній .
int res = 0;
Починаємо обхід у ширину.
while (!q.empty()) {
Виймаємо вершину з черги .
int v = q.front(); q.pop();
Ітеруємося по всіх ребрах, інцидентних вершині .
for (auto edge : g[v]) {
Розглянемо ребро з вагою .
int to = edge.first; int kto = edge.second;
Якщо вага ребра менша за , пропускаємо це ребро (перехід по ньому заборонено).
if (kto < k) continue;
Якщо вершина ще не відвідана, додаємо її до черги, позначаємо як відвідану і збільшуємо лічильник на .
if (used[to] == 0) { q.push(to); used[to] = 1; res++; } } }
Повертаємо відповідь на запит.
return res; }
Основна частина програми. Зчитуємо вхідні дані.
scanf("%d %d", &n, &qu); g.resize(n + 1); for (i = 0; i < n - 1; i++) { scanf("%d %d %d", &p, &q, &r);
Додаємо неорієнтоване ребро з вагою до списку суміжності.
g[p].push_back({ q, r }); g[q].push_back({ p, r }); }
Обробляємо запитів один за одним.
while (qu--) { scanf("%d %d", &k, &v);
Виконуємо пошук у ширину (BFS), починаючи з вершини , і виводимо кількість вершин, відвіданих під час BFS.
printf("%d\n", bfs(v, k)); }
Реалізація алгоритму — пошук у глибину (DFS)
Зберігаємо вхідний граф у вигляді списку суміжності .
vector<vector<pair<int, int>>> g;
Функція dfs виконує пошук у глибину, починаючи з вершини , і повертає кількість відвіданих вершин. Під час обходу ми переходимо лише по тих ребрах, вага яких не менша за задане значення .
int dfs(int v, int k) {
Позначаємо вершину як відвідану.
used[v] = 1;
Змінна зберігає кількість вершин, відвіданих у піддереві з коренем у під час DFS.
int res = 1;
Ітеруємося по всіх ребрах, інцидентних вершині .
for (auto edge : g[v]) {
Розглядаємо ребро з вагою .
int to = edge.first; int kto = edge.second;
Якщо вага менша за , пропускаємо це ребро (перехід по ньому заборонений).
if (kto < k) continue;
Якщо вершина ще не відвідана, виконуємо пошук у глибину з неї. Кількість відвіданих вершин у піддереві з коренем у додається до .
if (used[to] == 0) res += dfs(to, k); } return res; }
Основна частина програми. Зчитуємо вхідні дані.
scanf("%d %d", &n, &qu); g.resize(n + 1); for (i = 0; i < n - 1; i++) { scanf("%d %d %d", &p, &q, &r);
Додаємо неорієнтоване ребро з вагою до списку суміжності.
g[p].push_back({ q, r }); g[q].push_back({ p, r }); }
Обробляємо запитів по одному.
while (qu--) { scanf("%d %d", &k, &v);
Виконуємо пошук у глибину, починаючи з вершини . Виводимо кількість вершин, відвіданих під час DFS, виключаючи саму вершину .
used.assign(n + 1, 0); printf("%d\n", dfs(v, k) - 1); }
Задано дерево з вершинами. Ребра дерева мають вагу лише або . Потрібно знайти XOR-суму для всіх пар вершин. Обчисліть суму всіх таких XOR-сум.
Вхідні дані. У першому рядку задано число вершин у графі. Наступні рядків описують ребра. Кожен рядок містить три цілих числа — номери вершин, які з'єднує ребро (вершини нумеруються від до ), та вагу ребра ( або ).
Вихідні дані. Виведіть суму XOR-сум між усіма парами вершин.

5 1 2 1 2 3 1 2 4 0 4 5 1
6
Використовуючи обхід у глибину (DFS), обчислимо XOR-суму між вершиною та всіма іншими вершинами. Збережемо XOR-суму між вершиною та у масиві . Нехай — кількість одиниць, а — кількість нулів у масиві (). Тоді відповідь на задачу дорівнює .
Приклад
Розглянемо граф з умови прикладу.
Біля кожної вершини вказано значення , тобто XOR-суму шляху від вершини до . Якщо для деяких вершин і , тоді їх XOR дорівнює і не впливає на суму. Якщо , то XOR-сума між ними дорівнює і додається до відповіді.
Отже, кількість пар з дорівнює . Пари, що дають одиницю в XOR-сумі: .
Збережемо граф у вигляді списку суміжності . Оголосимо масив .
vector<vector<pair<int,int>>> g; vector<int> x;
Функція dfs виконує обхід у глибину та обчислює XOR-суму від вершини до . Поточна сума XOR задається як . Батьківська вершина потрібна для уникнення повернення назад.
void dfs(int v, int cur_xor, int p = -1) { x[v] = cur_xor; for (auto z : g[v]) { int to = z.first; int w = z.second; if (to != p) dfs(to, cur_xor ^ w, v); } }
Основна частина програми. Зчитування графа.
scanf("%d", &n); g.resize(n + 1); x.resize(n + 1); for (i = 1; i < n; i++) { scanf("%d %d %d", &u, &v, &d); g[u].push_back({v, d}); g[v].push_back({u, d}); }
Запускаємо DFS з вершини .
dfs(1, 0, -1);
Підраховуємо кількість нулів та одиниць у масиві .
ones = 0; for (i = 1; i <= n; i++) if (x[i] == 1) ones++; zeroes = n - ones;
Виводимо відповідь.
printf("%lld\n", 1LL * ones * zeroes);
Реалізація на Python
Функція dfs реалізує обхід у глибину (DFS), який обчислює XOR-суму між вершинами та . Поточна XOR-сума між та представлена як . Батьківська вершина — це .
def dfs(v, cur_xor = 0, p = -1): x[v] = cur_xor for to, w in g[v]: if to != p: dfs(to, cur_xor ^ w, v)
Основна частина програми. Зчитування графа.
n = int(input()) g = [[] for _ in range(n + 1)] for _ in range(n - 1): u, v, d = map(int, input().split()) g[u].append((v, d)) g[v].append((u, d))
Ініціалізуємо список .
x = [0] * (n + 1)
Запускаємо DFS з вершини .
dfs(1)
Обчислюємо кількість нулів і одиниць у масиві .
ones = sum(x) zeroes = n - ones
Виводимо відповідь.
print(ones * zeroes)
Задано дерево з вершинами, пронумерованими від до . -те ребро дерева з'єднує вершини та . Вершина пофарбована в колір (у цій задачі кольори задані цілими числами).
Вершина вважається хорошою, якщо найкоротший шлях від вершини до вершини не містить жодної іншої вершини такого самого кольору, як і у вершини , крім самої .
Знайдіть усі хороші вершини.
Вхідні дані. У першому рядку міститься одне ціле число — кількість вершин.
У другому рядку міститься цілих чисел — кольори вершин: .
Кожен з наступних рядків містить по два цілих числа та — ребра дерева.
Вивід. Виведіть усі хороші вершини по одній у рядку в порядку зростання їхніх номерів.

6 2 7 1 8 2 8 1 2 3 6 3 2 4 3 2 5
1 2 3 4 6
10 3 1 4 1 5 9 2 6 5 3 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10
1 2 3 5 6 7 8
Запустимо обхід у глибину (DFS) з вершини . При вході у вершину з кольором збільшуємо значення на . Змінна вказує, скільки разів колір зустрічається на шляху від вершини до вершини (включно). Якщо цей колір зустрічається рівно один раз, то вершина вважається "хорошою", і ми додаємо її до результату.
Після виходу з вершини зменшуємо на .
Приклад
Граф з першого прикладу виглядає наступним чином:
Вершина не є хорошою, тому що на шляху вершини та мають однаковий колір.
Вершина є хорошою, тому що на шляху кольори всіх проміжних вершин відрізняються від кольору вершини .
Збережемо вхідний граф у вигляді списку суміжності . Оголосимо масиви.
vector<int> used, color; vector<vector<int>> g; set<int> st;
Функція dfs реалізує обхід у глибину. Змінна — це батьківська вершина вершини .
void dfs(int v, int par) {
Вершина має колір . Позначимо, що вершина з цим кольором зустрічається на шляху від вершини .
used[color[v]]++;
Значення показує, скільки разів колір з'являється на шляху від вершини до вершини (включно). Якщо цей колір зустрічається рівно один раз, тоді вершина вважається хорошою, і ми додаємо її до множини результату .
if (used[color[v]] == 1) st.insert(v);
Пройдемося по всіх ребрах , що виходять з вершини . Якщо вершина не є батьком , запускаємо обхід у глибину з вершини . У такому випадку, батьком вершини стає вершина .
for (int to : g[v]) if (to != par) dfs(to, v);
Після виходу з вершини зменшуємо значення на .
used[color[v]]--; }
Основна частина програми. Зчитуємо кількість вершин і масив кольорів.
scanf("%d", &n); color.resize(n + 1); for (i = 1; i <= n; i++) scanf("%d", &color[i]);
Зчитуємо граф.
used.resize(100001); g.resize(n + 1); for (i = 1; i < n; i++) { scanf("%d %d", &x, &y); g[x].push_back(y); g[y].push_back(x); }
Запускаємо обхід у глибину з вершини .
dfs(1, 1);
Виводимо хороші вершини. Вони зберігаються в множині .
for (int val : st) printf("%d\n", val);
Задано дерево з вершинами та ребром. Вершини пронумеровані від до , і -те ребро з'єднує вершини та .
У вас є кольорів. Для кожної вершини дерева потрібно вибрати один із кольорів, дотримуючись наступної умови:
Якщо відстань між двома різними вершинами та менша або дорівнює двом, то та повинні бути пофарбовані в різні кольори.
Скількома способами можна розфарбувати дерево? Знайдіть відповідь за модулем .
Вхідні дані. Перший рядок містить два числа та . Кожен з наступних рядків містить два цілих числа та .
Вихідні дані. Виведіть кількість способів розфарбувати дерево за модулем
4 3 1 2 2 3 3 4
6
5 4 1 2 1 3 1 4 4 5
48
Почнемо розфарбовувати дерево з вершини . Її можна розфарбувати кольорами.
Припустимо, ми знаходимося у вершині . Якщо вона не має батька (), тоді її дітей можна розфарбувати кольорами. Якщо має батька, тоді дітей можна розфарбувати кольорами. Оскільки відстань між дітьми вершини дорівнює , вони не можуть мати однаковий колір.
Якщо вершина має батька, то першого сина можна розфарбувати кольорами, другого — і так далі.
Залишається перемножити кількість кольорів, якими можна розфарбувати кожну вершину.
Приклад
У першому прикладі існує способів розфарбування:
Розглянемо другий тест. Біля кожної вершини вкажемо кількість кольорів, якими її можна розфарбувати. Наприклад, вершину можна розфарбувати кольорами, оскільки її колір не повинен збігатися з кольорами вершин та . Загальна кількість способів розфарбувати дерево дорівнює .
Зберігаємо вхідний граф у списку суміжності . Оголошуємо константу для обчислення по модулю.
#define MOD 1000000007 vector<vector<int>> g;
Функція dfs повертає кількість способів розфарбувати дерево з коренем у вершині за модулем . Батько вершини — . Вершину можна розфарбувати кольорами.
int dfs(int v, int col, int p = -1) { long long res = col;
Ми перебуваємо у вершині . У змінній зберігаємо кількість кольорів, які не можна використовувати для розфарбування дітей вершини . Спочатку встановлюємо , бо діти не можуть мати такий самий колір, як у . Якщо має батька (), то діти також не можуть мати колір батька.
int cnt = 1; if (p >= 0) cnt++;
Ітеруємось по синах вершини .
for (int to : g[v]) if (to != p) {
Вершину можна розфарбувати кольорами. При переході до кожного сина значення збільшується на .
res = (res * dfs(to, k - cnt, v)) % MOD; cnt++; } return res; }
Основна частина програми. Зчитування вхідних даних.
scanf("%d %d", &n, &k); g.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); }
Починаємо обхід у глибину з вершини . Вершину номер можна розфарбувати кольорами.
res = dfs(1, k);
Виводимо відповідь.
printf("%lld\n", res);
Реалізація на Python
Встановлюємо максимальну глибину стеку інтерпретатора Python до вказаного ліміту.
import sys sys.setrecursionlimit(1000000)
Оголошуємо константу для обчислення по модулю.
MOD = 1000000007
Функція dfs повертає кількість способів розфарбувати дерево з коренем у вершині за модулем . Батько вершини — . Вершину можна розфарбувати кольорами.
def dfs(v, col, p = -1): res = col
Ми перебуваємо у вершині . У змінній зберігаємо кількість кольорів, які не можна використовувати для розфарбування дітей вершини . Спочатку встановлюємо , бо діти не можуть мати такий самий колір, як у . Якщо має батька (), то діти також не можуть мати колір батька.
cnt = 1 if p >= 0: cnt += 1
Ітеруємось по синах вершини .
for to in g[v]: if to != p:
Вершину можна розфарбувати кольорами. При переході до кожного сина значення збільшується на .
res = (res * dfs(to, k - cnt, v)) % MOD cnt += 1 return res
Основна частина програми. Зчитування вхідних даних.
n, k = map(int, input().split()) g = [[] for _ in range(n + 1)] for _ in range(n - 1): a, b = map(int, input().split()) g[a].append(b) g[b].append(a)
Починаємо обхід у глибину з вершини . Вершину номер можна розфарбувати кольорами.
res = dfs(1, k)
Виводимо відповідь.
print(res)
У країні є міст і двосторонніх доріг, так що можливо дістатися з будь-якого міста до будь-якого іншого, використовуючи лише ці дороги. Міста пронумеровані цілими числами від до включно.
Спочатку всі дороги вважаються поганими, але уряд планує покращити стан деяких із них. Громадяни будуть задоволені покращенням, якщо на шляху від столиці, розташованої у місті , до будь-якого іншого міста буде не більше ніж одна погана дорога.
Визначте кількість способів покращити якість деяких доріг, щоб задовольнити цю вимогу. Оскільки відповідь може бути дуже великою, виведіть її по модулю .
Вхідні дані. У першому рядку задано одне ціле число — кількість міст у країні. У другому рядку задано додатних цілих чисел , що описують дороги в країні. Число означає, що є дорога між містом та містом .
Вихідні дані. Виведіть кількість способів покращити якість доріг по модулю .

3 1 1
4
6 1 2 2 1 5
15
Нехай — кількість способів покращити якість доріг у піддереві з коренем у вершині . Нехай — один із дітей вершини .
Якщо дорога залишається у поганому стані, тоді всі дороги в піддереві з коренем у вершині повинні бути покращені.
Якщо дорогу покращено, тоді кількість способів покращити дороги в піддереві з коренем у вершині дорівнює .
Нехай — діти вершини . Тоді
Якщо — лист (тобто вершина без дітей), то покладемо .
Приклад
Розглянемо приклади, наведені в умові задачі. Для кожної вершини вкажемо значення .
У першому прикладі існує способи покращити якість доріг так, щоб на шляху від міста 1 до будь-якого іншого міста було не більше ніж одна погана дорога. Погані дороги позначені пунктиром, а покращені — суцільною лінією.
Усі обчислення виконуються по модулю .
#define MOD 1000000007
Функція dfs обчислює значення — кількість способів покращити дороги в піддереві з коренем у вершині . Батьком вершини є вершина .
long long dfs(int v, int p = -1) { long long s = 1;
Якщо — діти вершини , то
for (int to : g[v]) if (to != p) { long long sub = dfs(to, v); s = (s * (sub + 1)) % MOD; } return s; }
Зчитуємо вхідні дані. Побудова дерева.
scanf("%d", &n); g.resize(n + 1); for (i = 2; i <= n; i++) { scanf("%d", &p); g[i].push_back(p); g[p].push_back(i); }
Обчислюємо та виводимо результат.
res = dfs(1); printf("%lld\n", res);
У ясні літні дні Нюша полюбляє ловити метеликів на свіжому повітрі. Але сьогодні їй трапився особливо хитрий метелик — він залетів до лабіринту й намагається сховатися в ньому.
Лабіринт складається з кімнат, пронумерованих від до , деякі з яких з'єднані між собою коридорами. Відомо, що між будь-якими двома кімнатами існує рівно один простий шлях, який проходить лише через коридори. Іншими словами, лабіринт є деревом з вершинами та ребром.
Вхід до лабіринту розташований у кімнаті номер . Листом називається будь-яка кімната, яка з'єднана лише з однією іншою кімнатою і не є коренем (тобто не є кімнатою ). У кожному листі є вихід із лабіринту. Метелик починає свій політ із кімнати в напрямку одного з виходів. Він рухається з сталою швидкістю, ніколи не повертається назад і проходить один коридор за хвилину, переходячи в сусідню кімнату. Усі коридори однакової довжини.
Щоб зловити метелика, Нюша вирішила покликати на допомогу кількох друзів. Спочатку кожен з них може зайняти позицію в будь-якій кімнаті, де є вихід. Щойно метелик почне свій рух із входу в напрямку будь-якого виходу, друзі одразу можуть почати рух з виходів у напрямку до входу. Вони рухаються з тією ж швидкістю, що й метелик. Якщо хтось із них зустріне метелика — чи то в кімнаті, чи посередині коридору — вважається, що його спіймано. Якщо ж метелик досягне виходу, не зустрівши жодного з друзів, він успішно втече на волю.
Допоможи Нюші визначити мінімальну кількість друзів, необхідну для того, щоб гарантовано спіймати метелика незалежно від того, до якого виходу він полетить.
Вхідні дані. Перший рядок містить одне ціле число — кількість кімнат у лабіринті.
Наступні рядків описують коридори, які з'єднують кімнати. Кожен рядок містить два цілих числа та — номери кімнат, з'єднаних коридором.
Гарантується, що задана система коридорів утворює дерево.
Вихідні дані. Виведи одне ціле число — мінімальну кількість друзів, необхідну для гарантованого упіймання метелика.
3 1 2 1 3
2
4 1 2 2 3 2 4
1
Виконаємо обхід у глибину (DFS), починаючи з вершини . Для кожної вершини обчислюємо два значення:
— відстань від вершини до вершини . Якщо — батько вершини , то
— відстань від вершини до найближчого листа в піддереві з коренем у . Якщо — діти вершини , то
Якщо — лист дерева, то покладаємо .
Далі виконаємо другий обхід у глибину. Під час цього проходу обчислюється значення — мінімальна кількість друзів, яких потрібно розмістити в деяких листах піддерева з коренем у вершині , щоб гарантовано спіймати метелика, якщо він вирішить летіти саме в це піддерево.
Якщо , то . У цьому випадку достатньо одного друга, якого можна розмістити в листі з мінімальною глибиною в піддереві вершини : він дійде до вершини не пізніше, ніж метелик, що летить із кореня.
Інакше, якщо — діти вершини , тоді
Якщо метелика не вдається спіймати безпосередньо у вершині , то потрібно бути готовим перехопити його в будь-якому з піддерев дітей .
Остаточна відповідь до задачі — це значення .
Приклад
У першому прикладі (лівий малюнок) потрібно два друга, які мають бути розміщені біля двох виходів. Метелик може летіти з вершини або до вершини , або до вершини . В обох випадках його перехопить один із друзів Нюші.
У другому прикладі (правий малюнок) достатньо одного друга, якого можна розмістити або в вершині , або в вершині . Метелик за одну хвилину долетить до вершини , і за цей самий час друг дійде до неї з виходу та перехопить метелика.
Розглянемо наступний приклад.
Нюші знадобиться троє друзів, щоб спіймати метелика.
Оголосимо константу для нескінченності та масиви.
#define INF 2000000000 vector<vector<int> > g; vector<int> d, h, res;
Функція dfs виконує обхід у глибину, починаючи з вершини , і повертає значення . Тут — батько вершини , а — відстань від вершини до вершини . Під час обходу обчислюються значення та для кожної вершини .
int dfs(int v, int p = -1, int cnt = 0) {
Значення , яке дорівнює відстані від вершини до , зберігається у .
d[v] = cnt; int height = INF;
Проходимося по всіх дітях вершини . Розглядаємо ребро . Якщо збігається з батьком вершини , пропускаємо його і переходимо до наступної вершини.
for (int to : g[v]) if (to != p)
У змінній обчислюємо значення
де — діти вершини .
Якщо , тоді вершина є листком, і встановлюємо . Інакше повертаємо
return h[v] = (height == INF) ? 0 : 1 + height; }
Функція dfs2 виконує обхід у глибину, починаючи з вершини , і повертає значення . Тут — це батьківська вершина для вершини .
int dfs2(int v, int p = -1) { int s = 0;
Нехай — нащадки вершини . У змінній обчислюємо суму:
for (int to : g[v]) if (to != p) { dfs2(to, v); s += res[to]; }
Якщо , то одного друга достатньо, і . Інакше
return res[v] = (h[v] <= d[v]) ? 1 : s; }
Основна частина програми. Зчитування вхідних даних. Побудова графа.
scanf("%d", &n); g.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); }
Ініціалізація масивів.
d.resize(n + 1); h.resize(n + 1); res.resize(n + 1);
Запускаємо два обходи в глибину. Перший прохід обчислює значення та для кожної вершини .
dfs(1); dfs2(1);
Виведіть відповідь — мінімальну кількість друзів, необхідних для того, щоб спіймати метелика.
printf("%lld\n", res[1]);
Кефа вирішив відсвяткувати свою першу велику зарплату походом до ресторану.
Він живе поруч із незвичним парком. Парк представляє собою укорінене дерево з вершинами, вкорінене у вершині . Дім Кефи також знаходиться у вершині . На жаль, у цьому парку мешкають коти, і Кефа вже визначив вершини, в яких ці коти знаходяться.
У листках дерева парку розташовані ресторани. Кефа хоче вибрати ресторан, але він дуже боїться котів. Тому він не піде до ресторану, якщо на шляху від свого дому до ресторану зустріне більше ніж послідовних вершин із котами.
Ваше завдання — допомогти Кефі підрахувати кількість ресторанів, які він може безпечно відвідати.
Вхідні дані. У першому рядку міститься два цілих числа і — кількість вершин у дереві та максимальна кількість послідовних вершин із котами, яку Кефа може витримати.
У другому рядку міститься цілих чисел , де кожне дорівнює (у вершині немає кота) або (у вершині є кіт).
У наступних рядках описано ребра дерева у форматі , де і — вершини, з’єднані ребром.
Гарантується, що задана множина ребер утворює дерево.
Вихідні дані. Виведіть кількість листкових вершин, до яких Кефа може дістатися, якщо на шляху з дому до них він зустріне не більше ніж послідовних вершин із котами.

7 1 1 1 0 0 0 0 1 1 2 1 3 2 4 2 5 3 6 3 7
2
8 2 1 1 0 1 0 1 0 1 1 2 2 3 2 5 2 6 3 4 6 7 6 8
2
Почнімо обхід у глибину з вершини , де знаходиться дім Кефи. Одним із параметрів функції dfs буде кількість послідовних вершин із котами. Якщо у вершині ця кількість перевищить , то ми не продовжуватимемо пошук у глибину з цієї вершини. Потрібно порахувати кількість відвіданих листкових вершин. Вершина є листком, якщо її степінь дорівнює і вона не є коренем (тобто не вершина ).
Приклад
Розглянемо дерева з першого та другого прикладів.
У першому прикладі Кефа може пройти лише одну послідовну вершину з котом. Він зможе дістатися до ресторанів, розташованих у вершинах та .
У другому прикладі Кефа може пройти дві послідовні вершини з котами. Він зможе дістатися до ресторанів, розташованих у вершинах та .
Місця знаходження котів зберігаються в масиві . Дерево зберігається у вигляді списку суміжності .
vector<int> cats; vector<vector<int> > g;
Функція dfs виконує обхід у глибину з вершини і повертає кількість ресторанів, до яких можна дістатися з цієї вершини (за умови, що на шляху до них трапляється не більше ніж послідовних вершин із котами). Батьківська вершина вершини — це вершина . Кількість послідовних вершин із котами до вершини включно — .
int dfs(int v, int prev, int cnt) {
Якщо вже зустрілося більше ніж послідовних котів, подальший обхід у глибину з вершини не виконується.
if (cnt > m) return 0;
Якщо ми знаходимося в листковій вершині, збільшуємо кількість доступних ресторанів.
if (g[v].size() == 1 && prev != -1) return 1;
У змінній рахуємо кількість ресторанів, доступних з вершини .
int res = 0;
Перебираємо всі ребра , що виходять із вершини . Якщо вершина є батьківською для , не переходимо в неї.
for (int to : g[v]) if (to != prev) {
Якщо у вершині немає кота, присвоїти . Інакше присвоїти . Змінна зберігає кількість послідовних вершин із котами до та включно вершини .
int c = (cats[to] == 0) ? 0 : cnt + 1;
Розпочати обхід у глибину з вершини .
res += dfs(to, v, c); }
Повернути результат.
return res; }
Основна частина програми. Зчитати вхідні дані.
scanf("%d %d", &n, &m); cats.resize(n + 1); for (i = 1; i <= n; i++) scanf("%d", &cats[i]);
Побудувати дерево.
g.resize(n + 1); for (i = 1; i < n; i++) { scanf("%d %d", &a, &b); g[a].push_back(b); g[b].push_back(a); }
Вивести результат. Функція dfs, викликана з вершини , повертає відповідь на задачу. Оскільки вершина не має батьківської, другим параметром передаємо значення . Стартуючи з вершини , ми вже зустріли котів.
printf("%d\n", dfs(1, -1, cats[1]));
Реалізація мовою Python
Функція dfs виконує обхід у глибину з вершини і повертає кількість ресторанів, до яких можна дістатися з цієї вершини (за умови, що на шляху до них зустрічається не більше ніж послідовних вершин із котами). Батьківська вершина вершини — це . Кількість послідовних вершин із котами до вершини включно — це .
def dfs(v, prev, cnt):
Якщо вже зустрілося більше ніж послідовних котів, подальший обхід у глибину з вершини не виконується.
if cnt > m: return 0
Якщо ми знаходимося в листковій вершині, збільшуємо кількість доступних ресторанів.
if len(g[v]) == 1 and prev != -1: return 1
У змінній підраховується кількість ресторанів, до яких можна дістатися з вершини .
res = 0
Проітеруємо всі ребра , що виходять з вершини . Якщо вершина є батьківською для , не переходимо до неї.
for to in g[v]: if to != prev:
Якщо у вершині немає кота, встановити . Інакше присвоїти . Змінна зберігає кількість послідовних вершин із котами до та включно вершини .
c = 0 if cats[to] == 0 else cnt + 1
Розпочати обхід у глибину з вершини .
res += dfs(to, v, c)
Повернути результат.
return res
Основна частина програми. Зчитати вхідні дані.
n, m = map(int, input().split()) cats = [0] + list(map(int, input().split()))
Побудувати дерево.
g = [[] for _ in range(n + 1)] for _ in range(n - 1): a, b = map(int, input().split()) g[a].append(b) g[b].append(a)
Вивести результат. Функція dfs, викликана з вершини , повертає відповідь на задачу. Оскільки вершина не має батьківської, другим параметром передаємо значення . Починаючи з вершини , ми вже зустріли котів.
print(dfs(1, -1, cats[1]))
Задано дерево з вершинами. Вершина вважається коренем. Існує рівно один простий шлях між будь-якими двома вершинами.
Позначимо — кількість ребер на шляху від вершини до вершини .
Знайдіть кількість пар вершин таких, що виконується рівність:
Вхідні дані. Перший рядок містить одне ціле число — кількість вершин у дереві.
Кожен з наступних рядків містить по два цілих числа, які описують ребра дерева: пари вершин, з'єднаних ребром.
Вихідні дані. Виведіть одне число — кількість пар , для яких виконується .
5 1 2 2 3 2 4 4 5
13
Умова виконується для всіх пар вершин , де є предком вершини при обході в глибину, що починається з вершини .
Розглянемо приклад:
;
;
Позначимо глибину вершини як , тобто кількість вершин на шляху від кореня (вершини ) до . На рисунку глибина підписана поруч з кожною вершиною.
Зафіксуємо вершину та поставимо запитання: скільки існує таких вершин , що виконується умова ?
Наприклад, для є такі вершини: . Зазначимо, що . Отже, для фіксованої вершини існує рівно відповідних вершин .
Щоб розв’язати задачу, потрібно обчислити глибину для кожної вершини в дереві, а потім знайти суму всіх цих глибин по дереву.
Приклад
Розглянемо граф із прикладу. Поруч з кожною вершиною зазначено її глибину.
Сума глибин усіх вершин: .
Визначимо список суміжності графа як . Глибини вершин зберігаються в масиві .
vector<vector<int> > g; vector<int> h;
Функція dfs обчислює глибину кожної вершини . Батьком вершини є вершина .
int dfs(int v, int p) { for (int to : g[v])
Розглядаємо ребро . Якщо вершина не є батьком вершини , тоді обчислюємо її глибину та викликаємо dfs для неї.
if (to != p) { h[to] = h[v] + 1; dfs(to, v); } return h[v]; }
Основна частина програми. Зчитуємо кількість вершин у дереві.
scanf("%d", &n); g.resize(n + 1);
Будуємо дерево.
for (i = 2; i <= n; i++) { scanf("%d %d", &a, &b); g[a].push_back(b); g[b].push_back(a); }
Встановлюємо глибину вершини рівною .
h.resize(n + 1); h[1] = 1;
Запускаємо обхід у глибину з вершини .
dfs(1, -1);
Обчислюємо відповідь — суму глибин усіх вершин.
res = 0; for (i = 1; i <= n; i++) res += h[i];
Виводимо відповідь.
printf("%lld\n", res);
Дано бінарне дерево, яке є ациклічним зв'язним неорієнтованим графом з вершинами та ребрами. Кожна вершина має степінь не більше . Коренем є вершина номер , при цьому її степінь не перевищує .
На жаль, корінь дерева інфікований. Наступний процес повторюється разів:
Гусейн або вибирає вершину, яка ще не інфікована (і ще не видалена), та видаляє її разом з усіма інцидентними їй ребрами, або не робить нічого.
Після цього інфекція поширюється на всі вершини, з'єднані ребром з уже інфікованими вершинами (усі раніше інфіковані вершини залишаються інфікованими).
Допоможіть Гусейну визначити максимальну кількість вершин, які він може врятувати від інфекції (зауважимо, що видалені вершини не вважаються врятованими).
Вхідні дані. У першому рядку міститься число — кількість вершин у дереві.
Кожен з наступних рядків містить два цілих числа та — ребро дерева.
Гарантується, що граф є бінарним деревом з коренем у вершині .
Вихідні дані. Виведіть одне ціле число — кількість врятованих вершин.
4 1 2 2 3 2 4
2
7 1 2 1 5 2 3 2 4 5 6 5 7
2
Для кожної вершини обчислимо кількість вершин у піддереві, де вона є коренем. Якщо — діти вершини , тоді
Якщо вершина є листком дерева, то .
Нехай — це кількість врятованих вершин у піддереві з коренем у , за умови, що наразі інфікована тільки вершина (і ніхто більше з піддерева не інфікований).
Якщо у вершини лише одна дитина , тоді (видалена вершина не вважається врятованою).
Нехай вершина має двох дітей та (оскільки кожна вершина має степінь не більше ). Маємо два варіанти:
Видалити і рекурсивно рятувати вершини в піддереві з коренем . Кількість врятованих вершин: .
Видалити і рекурсивно рятувати вершини в піддереві з коренем . Кількість врятованих вершин: .
Серед двох варіантів вибираємо той, що дає більше врятованих вершин.
Розглянемо процес пошуку в глибину при обробці ребра .
Нехай — другий син вершини . Нам потрібно обчислити значення . Спробуймо знайти його, використовуючи лише вершини і . Ми знаємо, що:
Звідси випливає, що:
Таким чином,
Проходимо по всіх дітях вершини і обчислюємо:
Приклад
У першому прикладі Гусейн може врятувати дві вершини — та , якщо вибере вершину для видалення.
Розглянемо другий приклад. Поряд з кожною вершиною вказано позначення . Зеленим кольором позначені вершини, які видаляє Гусейн.
Наприклад,
Оголошуємо масиви.
#define MAX 300005 int sum[MAX], dp[MAX]; vector<vector<int>> g;
Функція dfs виконує обхід у глибину з вершини . Батьківська вершина для — це .
void dfs(int v, int p = -1) {
Ініціалізуємо значення та .
sum[v] = 1; dp[v] = 0;
Рекурсивно запускаємо обхід у глибину для всіх дітей вершини .
for (int to : g[v]) if (to != p) { dfs(to, v); sum[v] += sum[to]; }
Обчислюємо значення .
for (int to : g[v]) if (to != p) { dp[v] = max(dp[v], sum[to] - 1); // якщо є лише один син dp[v] = max(dp[v], dp[to] + sum[v] - sum[to] - 2); } }
Основна частина програми. Зчитування вхідних даних.
scanf("%d", &n); g.clear(); g.resize(n + 1); memset(dp, 0, sizeof(dp)); memset(sum, 0, sizeof(sum)); for (i = 1; i < n; i++) { scanf("%d %d", &u, &v); g[u].push_back(v); g[v].push_back(u); }
Виконаємо обхід у глибину, починаючи з вершини .
dfs(1);
Виведемо відповідь.
printf("%d\n", dp[1]);