Union-Find / conjuntos disjuntos
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| YS | Union Find | Fácil | DSU | en el módulo |
Recursos
| Fuente | Recurso | Notas |
|---|---|---|
| CF EDU | DSU | explicación en video + problemas de DSU |
| CSA | Disjoint Data Sets | ambas optimizaciones, diagramas |
| PAPS1 | 13.1 - Union-Find | ambas optimizaciones, sin diagramas |
| CPH | 15.2 - Union-Find | small to large, diagramas |
| IUSACO | 10.6 - Disjoint-Set Data Structure | compresión de caminos, diagramas |
| TC | Disjoint Set Data Structures | diagramas |
Implementación
| Fuente | Recurso | Notas |
|---|---|---|
| Benq (from KACTL) | DSU |
Abajo hay una implementación de un DSU. Utiliza fusión small-to-large y
compresión de caminos para ejecutar union-find rápidamente. sizes[x] guarda
el tamaño de la componente de , y parents[x] guarda el padre de
(igual a si es el representante).
#include <bits/stdc++.h>
using namespace std;
class DisjointSets {
private:
vector<int> parents;
vector<int> sizes;
public:
DisjointSets(int size) : parents(size), sizes(size, 1) {
for (int i = 0; i < size; i++) { parents[i] = i; }
}
/** @return el nodo "representante" de la componente de x */
int find(int x) { return parents[x] == x ? x : (parents[x] = find(parents[x])); }
/** @return si la fusión cambió la conectividad */
bool unite(int x, int y) {
int x_root = find(x);
int y_root = find(y);
if (x_root == y_root) { return false; }
if (sizes[x_root] < sizes[y_root]) { swap(x_root, y_root); }
sizes[x_root] += sizes[y_root];
parents[y_root] = x_root;
return true;
}
/** @return si x e y están en la misma componente conexa */
bool connected(int x, int y) { return find(x) == find(y); }
};import java.util.*;
public class DisjointSets {
int[] parents;
int[] sizes;
public DisjointSets(int size) {
parents = new int[size];
sizes = new int[size];
for (int i = 0; i < size; i++) {
parents[i] = i;
sizes[i] = 1;
}
}
/** @return el nodo "representante" de la componente de x */
public int find(int x) {
return parents[x] == x ? x : (parents[x] = find(parents[x]));
}
/** @return si la fusión cambió la conectividad */
public boolean unite(int x, int y) {
int xRoot = find(x);
int yRoot = find(y);
if (xRoot == yRoot) { return false; }
if (sizes[xRoot] < sizes[yRoot]) { return unite(yRoot, xRoot); }
parents[yRoot] = xRoot;
sizes[xRoot] += sizes[yRoot];
return true;
}
/** @return si x e y están en la misma componente conexa */
public boolean connected(int x, int y) { return find(x) == find(y); }
}class DisjointSets:
def __init__(self, size: int) -> None:
self.parents = [i for i in range(size)]
self.sizes = [1 for _ in range(size)]
def find(self, x: int) -> int:
""":return: el nodo "representante" de la componente de x"""
if self.parents[x] == x:
return x
self.parents[x] = self.find(self.parents[x])
return self.parents[x]
def unite(self, x: int, y: int) -> bool:
""":return: si la fusión cambió la conectividad"""
x_root = self.find(x)
y_root = self.find(y)
if x_root == y_root:
return False
if self.sizes[x_root] < self.sizes[y_root]:
x_root, y_root = y_root, x_root
self.parents[y_root] = x_root
self.sizes[x_root] += self.sizes[y_root]
return True
def connected(self, x: int, y: int) -> bool:
""":return: si x e y están en la misma componente conexa"""
return self.find(x) == self.find(y)Como la implementación es bastante simple, puede preferirse usarla en lugar de DFS para calcular componentes conexas.
Solución - Problema foco
Sin union-find, tendríamos que representar el grafo con una lista de adyacencia y usar flood fill para calcular componentes conexas. Este enfoque toma tiempo, que es demasiado lento, lo que nos motiva a usar union-find.
Al representar el grafo con la estructura de datos union-find que se implementó arriba, podemos usar sus métodos tanto para unir vértices como para comprobar si dos vértices y están en la misma componente conexa usando solo tiempo amortizado.
Esto reduce la complejidad temporal total a , que es una mejora sustancial y nos permite pasar todos los casos de prueba.
Implementación
Complejidad temporal:
#include <bits/stdc++.h>
using namespace std;
// BeginCodeSnip{DSU}
class DisjointSets {
private:
vector<int> parents;
vector<int> sizes;
public:
DisjointSets(int size) : parents(size), sizes(size, 1) {
for (int i = 0; i < size; i++) { parents[i] = i; }
}
/** @return el nodo "representante" de la componente de x */
int find(int x) { return parents[x] == x ? x : (parents[x] = find(parents[x])); }
/** @return si la fusión cambió la conectividad */
bool unite(int x, int y) {
int x_root = find(x);
int y_root = find(y);
if (x_root == y_root) { return false; }
if (sizes[x_root] < sizes[y_root]) { swap(x_root, y_root); }
sizes[x_root] += sizes[y_root];
parents[y_root] = x_root;
return true;
}
/** @return si x e y están en la misma componente conexa */
bool connected(int x, int y) { return find(x) == find(y); }
};
// EndCodeSnip
int main() {
int node_num, query_num;
cin >> node_num >> query_num;
DisjointSets dsu(node_num);
for (int i = 0; i < query_num; i++) {
int type, u, v;
cin >> type >> u >> v;
if (type == 0) {
dsu.unite(u, v);
} else {
cout << dsu.connected(u, v) << endl;
}
}
}import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) {
Kattio io = new Kattio();
int size = io.nextInt();
int queryNum = io.nextInt();
DisjointSets dsu = new DisjointSets(size);
for (int i = 0; i < queryNum; i++) {
int type = io.nextInt();
int u = io.nextInt();
int v = io.nextInt();
if (type == 0) {
dsu.unite(u, v);
} else {
if (dsu.connected(u, v)) {
io.println(1);
} else {
io.println(0);
}
}
}
io.close();
}
// CodeSnip{Kattio}
}
// BeginCodeSnip{DSU}
class DisjointSets {
int[] parents; // indexado desde 0
int[] sizes;
public DisjointSets(int size) {
parents = new int[size];
sizes = new int[size];
for (int i = 0; i < size; i++) {
parents[i] = i;
sizes[i] = 1;
}
}
/** @return el nodo "representante" de la componente de x */
public int find(int x) {
return parents[x] == x ? x : (parents[x] = find(parents[x]));
}
/** @return si la fusión cambió la conectividad */
public boolean unite(int x, int y) {
int xRoot = find(x);
int yRoot = find(y);
if (xRoot == yRoot) { return false; }
if (sizes[xRoot] < sizes[yRoot]) { return unite(yRoot, xRoot); }
parents[yRoot] = xRoot;
sizes[xRoot] += sizes[yRoot];
return true;
}
/** @return si x e y están en la misma componente conexa */
public boolean connected(int x, int y) { return find(x) == find(y); }
}
// EndCodeSnip# BeginCodeSnip{DSU}
class DisjointSets:
def __init__(self, size: int) -> None:
self.parents = [i for i in range(size)]
self.sizes = [1 for _ in range(size)]
def find(self, x: int) -> int:
""":return: el nodo "representante" de la componente de x"""
if self.parents[x] == x:
return x
self.parents[x] = self.find(self.parents[x])
return self.parents[x]
def unite(self, x: int, y: int) -> bool:
""":return: si la fusión cambió la conectividad"""
x_root = self.find(x)
y_root = self.find(y)
if x_root == y_root:
return False
if self.sizes[x_root] < self.sizes[y_root]:
x_root, y_root = y_root, x_root
self.parents[y_root] = x_root
self.sizes[x_root] += self.sizes[y_root]
return True
def connected(self, x: int, y: int) -> bool:
""":return: si x e y están en la misma componente conexa"""
return self.find(x) == self.find(y)
# EndCodeSnip
size, query_num = [int(i) for i in input().split()]
dsu = DisjointSets(size)
for _ in range(query_num):
q_type, u, v = [int(i) for i in input().split()]
if q_type == 0:
dsu.unite(u, v)
else:
print(1 if dsu.connected(u, v) else 0)Problemas
Estándar
Ya se deberían conocer las soluciones con DFS / búsqueda binaria de “Wormhole Sort” y “Moocast”.
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| CSES | Road Construction | Fácil | DSU | Solución | |
| Gold | Closing the Farm | Fácil | DSU | Solución | |
| Gold | ★ Mootube | Fácil | DSU | Solución | |
| Silver | ★ Wormhole Sort | Fácil | DSU | Solución | |
| Gold | Moocast | Fácil | DSU | Solución | |
| Old Silver | Tractor | Fácil | DSU | Solución | |
| CSA | Mountain Time | Normal | DSU | Solución | |
| CF | Two Sets | Normal | DSU | Solución | |
| Gold | Reachable Pairs | Normal | DSU | Solución |
Más difíciles
No hay que preocuparse por resolver estos si es la primera vez que se ve DSU.
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| CSES | New Roads Queries | Difícil | DSU, Merging | Solución | |
| Gold | Strongest Friendship Group | Difícil | DSU, Merging, Sorted Set | — | |
| Old Gold | Ski Course Rating | Difícil | DSU | Solución | |
| onlinejudge.org | War | Difícil | DSU | Solución | |
| Baltic OI | 2016 - Park | Muy difícil | DSU | Solución | |
| Gold | ★ Favorite Colors | Muy difícil | DSU | — | |
| Platinum | Valleys | Muy difícil | DSU | Solución |