Что такое DSU?
DSU — это структура данных, которая быстро отвечает на два вопроса:
- находятся ли два элемента в одном множестве
- как объединить два множества
Когда используется?
- Компоненты связности
- Алгоритм Краскала
- Проверка связности
- Offline-запросы
- Объединение групп
Главная идея
Каждый элемент знает своего родителя.
Здесь есть два множества:
Массив parent
int parent[N];
parent[x] — родитель вершины.
Если
parent[x] == x
то x является корнем множества.
Инициализация
Сначала каждый элемент находится сам по себе.
for (int i = 1; i <= n; i++) {
parent[i] = i;
}
Получается:
Функция find()
find(x) возвращает корень множества.
int find(int x) {
if (parent[x] == x) {
return x;
}
return find(parent[x]);
}
Пример:
find(3) вернет 1.
Сжатие путей
После первого find дерево можно сделать короче.
int find(int x) {
if (parent[x] == x) {
return x;
}
return parent[x] = find(parent[x]);
}
Было:
После find(4):
Объединение множеств
Чтобы объединить два множества:
void unite(int a, int b) {
a = find(a);
b = find(b);
if (a != b) {
parent[b] = a;
}
}
Пример объединения
Было:
unite(1,2)
unite(2,3)
Теперь
find(3) == 1
Задача 1
Есть n элементов. Нужно выполнять два типа запросов.
get должен вывести YES, если вершины находятся в одном множестве.
Input
5 5
union 1 2
union 2 3
get 1 3
get 1 5
get 4 5
Output
YES
NO
NO
Полная реализация
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int parent[N];
int find(int x) {
if (parent[x] == x) {
return x;
}
return parent[x] = find(parent[x]);
}
void unite(int a, int b) {
a = find(a);
b = find(b);
if (a != b) {
parent[b] = a;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; i++) {
parent[i] = i;
}
while (q--) {
string op;
int a, b;
cin >> op >> a >> b;
if (op == "union") {
unite(a, b);
}
else {
if (find(a) == find(b)) {
cout << "YES\n";
}
else {
cout << "NO\n";
}
}
}
}
Объединение по размеру
Можно хранить размер каждого множества.
int sz[N];
При объединении маленькое дерево цепляется к большому.
void unite(int a, int b) {
a = find(a);
b = find(b);
if (a == b) {
return;
}
if (sz[a] < sz[b]) {
swap(a, b);
}
parent[b] = a;
sz[a] += sz[b];
}
Размер множества
После объединений размер множества хранится только у корня.
sz[find(x)]
Например:
Задача 2
После каждого объединения вывести размер множества.
Input
5 3
1 2
2 3
4 5
Output
2
3
2
DSU + Краскал
DSU используется в алгоритме Краскала.
Если две вершины находятся в разных множествах, то ребро можно добавить.
if (find(a) != find(b)) {
unite(a, b);
}
Асимптотика
| Операция | Время |
|---|---|
| find() | O(α(n)) |
| union() | O(α(n)) |
Краткий шаблон
int parent[N];
int find(int x) {
if (parent[x] == x) {
return x;
}
return parent[x] = find(parent[x]);
}
void unite(int a, int b) {
a = find(a);
b = find(b);
if (a != b) {
parent[b] = a;
}
}