Graph Composition
Explicación
La idea clave es concentrarse en las componentes conexas de ambos grafos. Cualquier arista de que conecta vértices de distintas componentes de es problemática porque crea una conexión que no existe en . Esas aristas son “inválidas” y hay que eliminarlas. Contar esas aristas inválidas nos da la primera parte de la solución: la cantidad de aristas que hay que quitar de .
Tras quitar esas aristas inválidas, podría terminar con más componentes conexas que . Para corregirlo, hay que agregar aristas a para fusionar esas componentes extra hasta que la cantidad de componentes conexas de coincida con la de . Cada componente adicional de requiere agregar una arista para fusionarla con otra componente.
Finalmente, calculamos el número mínimo de operaciones sumando estos dos valores:
- La cantidad de aristas inválidas que hay que quitar
- La cantidad de componentes conexas extra de que hay que fusionar
Implementación
Complejidad temporal:
#include <functional>
#include <iostream>
#include <map>
#include <vector>
using std::cout;
using std::function;
using std::pair;
using std::vector;
int main() {
int test_num;
std::cin >> test_num;
for (int t = 0; t < test_num; t++) {
int n, m1, m2;
std::cin >> n >> m1 >> m2;
vector<vector<int>> adj_f(n), adj_g(n);
for (int i = 0; i < m1; i++) {
int u, v;
std::cin >> u >> v;
adj_f[--u].push_back(--v);
adj_f[v].push_back(u);
}
for (int i = 0; i < m2; i++) {
int u, v;
std::cin >> u >> v;
adj_g[--u].push_back(--v);
adj_g[v].push_back(u);
}
int f_comps = 0, g_comps = 0;
vector<int> component_id(n);
vector<int> used(n);
function<void(int, int)> dfs_g = [&](int node, int c) -> void {
component_id[node] = c;
used[node] = 1;
for (int u : adj_g[node]) {
if (!used[u]) { dfs_g(u, c); }
}
};
for (int i = 0; i < n; i++) {
if (!used[i]) {
g_comps++;
dfs_g(i, g_comps);
}
}
int invalid = 0; // aristas inválidas en el grafo F
std::map<pair<int, int>, bool> invalid_edges;
for (int i = 0; i < n; i++) {
for (int u : adj_f[i]) {
if (component_id[i] != component_id[u]) {
invalid++;
invalid_edges[{i, u}] = true;
}
}
}
function<void(int)> dfs_f = [&](int node) -> void {
used[node] = 1;
for (int u : adj_f[node]) {
if (!used[u] && !invalid_edges[{node, u}]) { dfs_f(u); }
}
};
used.assign(n, 0);
for (int i = 0; i < n; i++) {
if (!used[i]) {
f_comps++;
dfs_f(i);
}
}
cout << invalid / 2 + (f_comps - g_comps) << '\n';
}
}Implementación con DSU
Complejidad temporal:
#include <array>
#include <iostream>
#include <numeric>
#include <vector>
using std::array;
using std::cout;
using std::vector;
/**
* Es básicamente el mismo DSU del módulo,
* solo que ahora también lleva la cuenta de la cantidad de componentes conexas.
*/
struct DisjointSets {
int n, connected_comps;
vector<int> parents, sizes;
DisjointSets(int n) : n(n), connected_comps(n) {
parents = vector<int>(n);
iota(begin(parents), end(parents), 0);
sizes = vector<int>(n, 1);
}
int get(int x) {
if (x == parents[x]) { return x; }
return parents[x] = get(parents[x]);
}
bool unite(int x, int y) {
int x_root = get(x);
int y_root = get(y);
if (x_root == y_root) { return false; }
if (sizes[x_root] < sizes[y_root]) { std::swap(x_root, y_root); }
sizes[x_root] += sizes[y_root];
parents[y_root] = x_root;
connected_comps--;
return true;
}
bool connected(int x, int y) { return (get(x) == get(y)); }
};
int main() {
int test_num;
std::cin >> test_num;
for (int t = 0; t < test_num; t++) {
int n, m1, m2;
std::cin >> n >> m1 >> m2;
vector<array<int, 2>> f_edges(m1);
for (int i = 0; i < m1; i++) {
int a, b;
std::cin >> a >> b;
f_edges[i] = {--a, --b};
}
DisjointSets g(n);
for (int i = 0; i < m2; i++) {
int a, b;
std::cin >> a >> b;
g.unite(--a, --b);
}
DisjointSets f(n);
int min_ops = 0;
for (int i = 0; i < m1; i++) {
if (g.connected(f_edges[i][0], f_edges[i][1])) {
f.unite(f_edges[i][0], f_edges[i][1]);
} else {
min_ops++;
}
}
min_ops += f.connected_comps - g.connected_comps;
cout << min_ops << '\n';
}
}