Introducción a los grafos
Introducción
Los grafos se pueden usar para representar muchas cosas, desde imágenes hasta señales inalámbricas, pero una de las analogías más simples es un mapa. Consideremos un mapa con varias ciudades y caminos bidireccionales que las conectan. Algunos problemas relacionados con grafos son:
-
¿La ciudad está conectada con la ciudad ? Consideremos que una región es un grupo de ciudades tal que cada ciudad del grupo puede alcanzar a cualquier otra ciudad de ese grupo, pero a ninguna otra. ¿Cuántas regiones hay en este mapa, y qué ciudades están en cada región? (USACO Plata)
-
¿Cuál es la distancia más corta que hay que recorrer para ir de la ciudad a la ciudad ? (USACO Oro)
Para USACO Bronce, alcanza con aprender lo básico de cómo se representan los grafos (por lo general, listas de adyacencia).
| Fuente | Recurso | Notas |
|---|---|---|
| CSA | Introduction to Graphs | interactivo |
| CSA | Graph Representations | interactivo — listas y matrices de adyacencia |
| CSA | Graph Editor | usá esta herramienta para visualizar tus propios grafos |
| CPH | 11 - Basics of Graphs | terminología y representación de grafos |
| IUSACO | 10.1 to 10.3 - Graph Theory | conceptos básicos y representación de grafos, árboles |
| PAPS | 6.4 - Graphs | matrices de adyacencia, listas, mapas |
Construir listas de adyacencia
Los grafos suelen darse como entrada en el siguiente formato:
- La primera línea contiene la cantidad de nodos y la cantidad de aristas .
- Luego siguen líneas, cada una con un par de enteros que especifica una arista del grafo.
Por ejemplo, el grafo no dirigido dado en el recurso de CSAcademy de arriba se representaría con la siguiente entrada:
6 10
2 4
0 2
0 4
0 5
5 3
2 3
1 3
4 5
4 1
1 5y se podría visualizar en el editor de grafos de CSAcademy :

El siguiente código representa el grafo con listas de adyacencia. Una vez que tenemos el grafo en esta representación, es fácil imprimir la cantidad de vecinos de un nodo, o iterar sobre los vecinos de un nodo.
#include <iostream>
#include <vector>
using namespace std;
int main() {
int N, M;
cin >> N >> M;
vector<vector<int>> adj(N);
for (int i = 0; i < M; ++i) {
int u, v;
cin >> u >> v;
adj.at(u).push_back(v);
adj.at(v).push_back(u);
}
{
int u = 1;
// imprimir la cantidad de vértices adyacentes a u
cout << "deg(u) = " << adj.at(u).size() << endl;
// imprimir todas las aristas que tienen a u como extremo
for (int v : adj.at(u)) cout << "{" << u << ", " << v << "}" << "\n";
}
}import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer s1 = new StringTokenizer(reader.readLine());
int N = Integer.parseInt(s1.nextToken());
int M = Integer.parseInt(s1.nextToken());
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i < N; i++) { adj.add(new ArrayList<>()); }
for (int i = 0; i < M; i++) {
StringTokenizer s2 = new StringTokenizer(reader.readLine());
int u = Integer.parseInt(s2.nextToken());
int v = Integer.parseInt(s2.nextToken());
adj.get(u).add(v);
adj.get(v).add(u);
}
int u = 1;
// imprimir la cantidad de vértices adyacentes a u
System.out.println("deg(u) = " + adj.get(u).size());
// imprimir todas las aristas que tienen a u como extremo
for (int v : adj.get(u)) { System.out.println("{" + u + ", " + v + "}"); }
}
}N, M = map(int, input().split())
adj = [[] for _ in range(N)]
for i in range(M):
u, v = map(int, input().split())
adj[u].append(v)
adj[v].append(u)
u = 1
# imprimir la cantidad de vértices adyacentes a u
print("deg(u) =", len(adj[u]))
# imprimir todas las aristas que tienen a u como extremo
for v in adj[u]:
print("{" + str(u) + ", " + str(v) + "}")Salida:
deg(u) = 3
{1, 3}
{1, 4}
{1, 5}¿Cómo se ve un problema de grafos de Bronce?
Todos los problemas de abajo caen en al menos una de las siguientes dos categorías:
- La estructura del grafo es especial (es un árbol, un camino o un ciclo).
- Para resolver el problema, alcanza con iterar sobre la lista de adyacencia de cada vértice.
Además, el conocimiento de temas de grafos de nivel Plata suele ayudar, pero no es estrictamente necesario para resolver el problema.
Livestock Lineup
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| Bronze | Livestock Lineup | Difícil | Permutation, Graph | Solución |
Aunque la solución prevista es probar por fuerza bruta todas las permutaciones posibles de las vacas en tiempo , podemos resolver el problema en solo si representamos las restricciones con un grafo.
Solución usando grafos
Nótese que, como se garantiza que la entrada es válida, siempre vamos a terminar con “cadenas” de vacas que podemos ordenar como queramos. Usando el sample del problema, obtendríamos una representación en “cadenas” como esta:

Nótese que las vacas que no forman parte de ninguna cadena se pueden considerar cadenas propias de longitud 1 a los efectos de la implementación.
Con esta representación en mente, podemos iterar las vacas en orden lexicográfico (alfabético). Cuando visitamos una vaca que podría ser un inicio posible de una cadena (una vaca que tiene a lo sumo un vecino requerido), recorremos repetidamente a sus vecinos, agregando las vacas que visitamos al ordenamiento, hasta llegar a un extremo.
Implementación
Complejidad temporal:
#include <algorithm>
#include <fstream>
#include <map>
#include <string>
#include <vector>
using std::endl;
using std::string;
using std::vector;
// BeginCodeSnip{Cow Names}
// Expresión lambda — ver /general/lambda-funcs
const vector<string> COWS = []() {
vector<string> tmp{"Bessie", "Buttercup", "Belinda", "Beatrice",
"Bella", "Blue", "Betsy", "Sue"};
// ordenar los nombres lexicográficamente
std::sort(std::begin(tmp), std::end(tmp));
return tmp;
}();
// EndCodeSnip
int main() {
std::map<string, int> cow_inds;
for (int i = 0; i < COWS.size(); i++) { cow_inds[COWS[i]] = i; }
std::ifstream read("lineup.in");
int req_num;
read >> req_num;
vector<vector<int>> neighbors(COWS.size());
for (int r = 0; r < req_num; r++) {
string cow1;
string cow2;
string trash;
read >> cow1 >> trash >> trash >> trash >> trash >> cow2;
// Convertir los nombres a su índice en la lista
int c1 = cow_inds[cow1];
int c2 = cow_inds[cow2];
neighbors[c1].push_back(c2);
neighbors[c2].push_back(c1);
}
vector<int> order;
vector<bool> added(COWS.size());
for (int c = 0; c < COWS.size(); c++) {
if (!added[c] && neighbors[c].size() <= 1) {
added[c] = true;
order.push_back(c);
if (neighbors[c].size() == 1) {
int prev = c;
int at = neighbors[c][0];
while (neighbors[at].size() == 2) {
added[at] = true;
order.push_back(at);
int a = neighbors[at][0];
int b = neighbors[at][1];
int temp_at = a == prev ? b : a;
prev = at;
at = temp_at;
}
added[at] = true;
order.push_back(at);
}
}
}
std::ofstream out("lineup.out");
for (int c : order) { out << COWS[c] << endl; }
}import java.io.*;
import java.util.*;
public class LineUp {
// Se asume que está en orden (y lo está)
static final String[] COWS = new String[] {
"Beatrice", "Belinda", "Bella", "Bessie", "Betsy", "Blue", "Buttercup", "Sue"};
public static void main(String[] args) throws IOException {
Map<String, Integer> cowInds = new HashMap<>();
for (int i = 0; i < COWS.length; i++) { cowInds.put(COWS[i], i); }
BufferedReader read = new BufferedReader(new FileReader("lineup.in"));
int reqNum = Integer.parseInt(read.readLine());
List<Integer>[] neighbors = new ArrayList[COWS.length];
for (int c = 0; c < COWS.length; c++) { neighbors[c] = new ArrayList<>(); }
for (int r = 0; r < reqNum; r++) {
String[] words = read.readLine().split(" ");
// Convertir los nombres a su índice en la lista
int cow1 = cowInds.get(words[0]);
int cow2 = cowInds.get(words[words.length - 1]);
neighbors[cow1].add(cow2);
neighbors[cow2].add(cow1);
}
List<Integer> order = new ArrayList<>();
boolean[] added = new boolean[COWS.length];
for (int c = 0; c < COWS.length; c++) {
/*
* Comprobar que:
* 1. Esta vaca todavía no se agregó.
* 2. Esta vaca podría ser el inicio de una cadena.
*/
if (!added[c] && neighbors[c].size() <= 1) {
added[c] = true;
order.add(c);
// Si la longitud de la cadena > 1, seguimos
if (neighbors[c].size() == 1) {
int prev = c;
int at = neighbors[c].get(0);
while (neighbors[at].size() == 2) {
added[at] = true;
order.add(at);
int a = neighbors[at].get(0);
int b = neighbors[at].get(1);
int temp_at = a == prev ? b : a;
prev = at;
at = temp_at;
}
// Agregar el último elemento
added[at] = true;
order.add(at);
}
}
}
PrintWriter out = new PrintWriter("lineup.out");
for (int c : order) { out.println(COWS[c]); }
out.close();
}
}COWS = sorted(
["Bessie", "Buttercup", "Belinda", "Beatrice", "Bella", "Blue", "Betsy", "Sue"]
)
cow_inds = {c: i for i, c in enumerate(COWS)}
neighbors = [[] for _ in range(len(COWS))]
with open("lineup.in") as read:
for _ in range(int(read.readline())):
words = read.readline().strip().split()
# Convertir los nombres a su índice en la lista
cow1 = cow_inds[words[0]]
cow2 = cow_inds[words[-1]]
neighbors[cow1].append(cow2)
neighbors[cow2].append(cow1)
order = []
added = [False for _ in range(len(COWS))]
for c in range(len(COWS)):
"""
Comprobar que:
1. Esta vaca todavía no se agregó.
2. Esta vaca podría ser el inicio de una cadena.
"""
if not added[c] and len(neighbors[c]) <= 1:
added[c] = True
order.append(c)
# Si la longitud de la cadena > 1, seguimos
if len(neighbors[c]) == 1:
prev = c
at = neighbors[c][0]
while len(neighbors[at]) == 2:
added[at] = True
order.append(at)
a, b = neighbors[at]
at, prev = b if a == prev else a, at
# Agregar el último elemento
added[at] = True
order.append(at)
with open("lineup.out", "w") as out:
for c in order:
print(COWS[c], file=out)Comprobá tu comprensión
Pregunta 1/6
Problemas
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| Bronze | The Great Revegetation | Difícil | Coloring | Solución | |
| Bronze | ★ Milk Factory | Difícil | Tree, DFS | Solución | |
| Bronze | Hoofball | Difícil | Functional Graph | Solución | |
| Bronze | Swapity Swap | Difícil | Permutation, Cycle | Solución | |
| Bronze | Cow Evolution | Muy difícil | Tree | Solución | |
| Bronze | Family Tree | Muy difícil | Tree | Solución |