Skip to Content

Caminos más cortos con aristas no ponderadas

Camino más corto no ponderado

HechoFuenteNombreDificultadTagsSolución
CSESMessage RouteFácilBFSen el módulo

Solución - Message Route

Podemos observar que hay muchos caminos más cortos posibles para imprimir. Por suerte, el problema indica que se puede imprimir cualquier solución válida. Nótese que, como en todo problema de BFS, la distancia de cada nodo aumenta en 11 cuando viajamos al siguiente nivel de nodos no visitados. Sin embargo, el problema pide información adicional: en este caso, el camino. Cuando atravesamos de un nodo aa a un nodo bb, podemos fijar el padre de bb como aa. Después de que el BFS termine, esto nos permite retroceder por los padres hasta llegar al nodo de partida. Sabemos que hay que terminar en el nodo 11 porque es el nodo inicial. Si no hay camino hasta el nodo final, su distancia permanecerá en INT_MAX.

Para la entrada de ejemplo, empezamos con el siguiente arreglo de padres.

Nodo12345
Padre00000
Distancia0INT_MAXINT_MAXINT_MAXINT_MAX

Después de visitar los hijos del nodo 11:

Nodo12345
Padre01110
Distancia0111INT_MAX

Después de visitar el nodo 55 desde el nodo 44:

Nodo12345
Padre01114
Distancia01112

Para determinar el camino, podemos retroceder desde el nodo n1n \rightarrow 1, en este caso 515 \rightarrow 1, empujando cada valor por el que retrocedemos a un vector. El camino que tomamos es 5parent[5]=4parent[4]=15 \rightarrow \texttt{parent}[5]=4 \rightarrow \texttt{parent}[4] =1, que corresponde al vector [5,4,1][5, 4, 1]. Nos detenemos en el nodo 11 porque era el nodo inicial. Por último, invertimos el vector e imprimimos su longitud (en este caso, 33).

Implementación

Complejidad temporal: O(V+E)\mathcal{O}(V+E)

#include <bits/stdc++.h> using namespace std; using vi = vector<int>; #define pb push_back int main() { int N, M; cin >> N >> M; vi dist(N + 1, INT_MAX), parent(N + 1); vector<vi> adj(N + 1); while (M--) { int a, b; cin >> a >> b; adj[a].pb(b), adj[b].pb(a); } queue<int> q; dist[1] = 0; q.push(1); while (!q.empty()) { int x = q.front(); q.pop(); for (int t : adj[x]) if (dist[t] == INT_MAX) { dist[t] = dist[x] + 1; parent[t] = x; q.push(t); } } if (dist[N] == INT_MAX) cout << "IMPOSSIBLE"; else { cout << dist[N] + 1 << "\n"; vi v{N}; while (v.back() != 1) v.pb(parent[v.back()]); reverse(begin(v), end(v)); for (int t : v) cout << t << " "; } }
import java.io.*; import java.util.*; public class Solution { // CodeSnip{Kattio} private static Map<Integer, LinkedList<Integer>> adj = new HashMap<>(); public static void main(String[] args) { Kattio io = new Kattio(); int n = io.nextInt(), m = io.nextInt(); for (int i = 0; i < m; i++) { int a = io.nextInt(), b = io.nextInt(); if (adj.get(a) == null) { adj.put(a, new LinkedList<>()); } if (adj.get(b) == null) { adj.put(b, new LinkedList<>()); } adj.get(a).add(b); adj.get(b).add(a); } int[] prev = new int[n + 1], dist = new int[n + 1]; Arrays.fill(dist, Integer.MAX_VALUE); dist[1] = 1; Queue<Integer> bfs = new LinkedList<>(); bfs.add(1); while (!bfs.isEmpty()) { int top = bfs.poll(); if (dist[top] == Integer.MAX_VALUE) continue; if (adj.get(top) != null) { for (int e : adj.get(top)) { if (dist[e] == Integer.MAX_VALUE) { dist[e] = dist[top] + 1; prev[e] = top; bfs.add(e); } } } } if (dist[n] == Integer.MAX_VALUE) { System.out.println("IMPOSSIBLE"); } else { System.out.println(dist[n]); int[] res = new int[dist[n]]; int i = dist[n] - 1; for (int x = n; x != 0; x = prev[x]) { res[i--] = x; } for (int a : res) System.out.print(a + " "); } } }
from collections import deque, defaultdict n, m = map(int, input().split()) edges = [] for _ in range(m): a, b = map(int, input().split()) edges.append((a, b)) graph = defaultdict(list) for a, b in edges: graph[a].append(b) graph[b].append(a) # Preparación del BFS queue = deque([1]) distance = [-1] * (n + 1) parent = [-1] * (n + 1) distance[1] = 0 while queue: node = queue.popleft() for neighbor in graph[node]: if distance[neighbor] == -1: # Si el vecino no fue visitado distance[neighbor] = distance[node] + 1 parent[neighbor] = node queue.append(neighbor) if neighbor == n: # Si llegamos al destino # Reconstruir el camino path = [] current = n while current != -1: path.append(current) current = parent[current] path.reverse() print(len(path)) print(" ".join(map(str, path))) exit() # Si no se encontró camino print("IMPOSSIBLE")

Extensión: BFS 0-1

HechoFuenteNombreDificultadTagsSolución
Baltic OI2013 - Tracks in the SnowFácilBFSen el módulo

Un BFS 0-1 halla el camino más corto en un grafo donde los pesos de las aristas solo pueden ser 0 o 1, y corre en O(V+E)\mathcal{O}(V + E) usando un deque. Leer el recurso de abajo para una explicación de cómo funciona el algoritmo.

Recursos
FuenteRecursoNotas
cp-algo0-1 BFS

aplicaciones habituales

Solución - Tracks in the Snow

Podemos usar la siguiente estrategia voraz para hallar la respuesta:

  • Ejecutar flood fill para hallar cada componente conexa con las mismas huellas.
  • Construir un grafo donde los nodos son las componentes conexas y hay aristas entre componentes conexas adyacentes.
  • La respuesta es la distancia máxima desde el nodo que contiene (1,1)(1, 1) hasta otro nodo. Podemos usar BFS para hallar esa distancia.

Para una demostración detallada de por qué funciona, ver el editorial oficial .

Aunque esto nos da una solución O(NM)\mathcal O(NM), hay una solución más simple usando BFS 0-1.

Consideremos el grafo con una arista entre cada par de celdas adyacentes con huellas, donde el peso es 0 si las huellas son iguales y 1 en caso contrario. La respuesta es simplemente el más largo de los caminos más cortos desde la celda superior izquierda. Esto es porque ir de una huella a otra igual es como no salir de un nodo (por eso el costo es 00), mientras que ir de una huella a una distinta es como atravesar la arista entre dos nodos (por eso el costo es 11).

Como el peso de cada arista es 0 o 1 y queremos los caminos más cortos desde la celda superior izquierda hasta cada otra celda, podemos aplicar BFS 0-1. La complejidad temporal de esta solución es O(NM)\mathcal O(NM).

Implementación

Complejidad temporal: O(NM)\mathcal O(NM)

#include <bits/stdc++.h> using namespace std; int dx[4]{1, -1, 0, 0}, dy[4]{0, 0, 1, -1}; int n, m, depth[4000][4000], ans = 1; string snow[4000]; bool inside(int x, int y) { return (x > -1 && x < n && y > -1 && y < m && snow[x][y] != '.'); } int main() { iostream::sync_with_stdio(false); cin.tie(0); cin >> n >> m; for (int i = 0; i < n; i++) cin >> snow[i]; deque<pair<int, int>> q; q.push_back({0, 0}); depth[0][0] = 1; while (q.size()) { pair<int, int> c = q.front(); q.pop_front(); ans = max(ans, depth[c.first][c.second]); for (int i = 0; i < 4; i++) { int x = c.first + dx[i], y = c.second + dy[i]; if (inside(x, y) && depth[x][y] == 0) { if (snow[x][y] == snow[c.first][c.second]) { depth[x][y] = depth[c.first][c.second]; q.push_front({x, y}); } else { depth[x][y] = depth[c.first][c.second] + 1; q.push_back({x, y}); } } } } cout << ans; return 0; }
import java.io.*; import java.util.*; public class tracks { static final int[] dx = {0, 0, -1, 1}; static final int[] dy = {-1, 1, 0, 0}; static int N = 1, H, W; static int[][] grid, count; public static void main(String[] args) { FastIO io = new FastIO(); H = io.nextInt(); W = io.nextInt(); grid = new int[H][W]; for (int i = 0; i < H; i++) { String line = io.next(); for (int j = 0; j < W; j++) { grid[i][j] = (line.charAt(j) == 'F') ? 1 : (line.charAt(j) == 'R') ? 2 : -1; } } io.println(bfs()); io.close(); } private static int bfs() { count = new int[H][W]; LinkedList<int[]> q = new LinkedList<>(); q.add(new int[] {0, 0}); count[0][0] = 1; while (!q.isEmpty()) { int[] curr = q.poll(); N = Math.max(N, count[curr[0]][curr[1]]); for (int i = 0; i < 4; i++) { int nx = curr[0] + dx[i]; int ny = curr[1] + dy[i]; if (nx < 0 || ny < 0 || nx >= H || ny >= W) continue; if (count[nx][ny] > 0) continue; if (grid[nx][ny] == -1) continue; if (grid[nx][ny] != grid[curr[0]][curr[1]]) { count[nx][ny] = count[curr[0]][curr[1]] + 1; q.addLast(new int[] {nx, ny}); } else { count[nx][ny] = count[curr[0]][curr[1]]; q.addFirst(new int[] {nx, ny}); } } } return N; } // BeginCodeSnip{FastIO} private static class FastIO extends PrintWriter { private InputStream stream; private byte[] buf = new byte[1 << 16]; private int curChar, numChars; // entrada estándar public FastIO() { this(System.in, System.out); } public FastIO(InputStream i, OutputStream o) { super(o); stream = i; } // entrada por archivo public FastIO(String i, String o) throws IOException { super(new FileWriter(o)); stream = new FileInputStream(i); } // lanza InputMismatchException() si se detectó previamente el fin de archivo private int nextByte() { if (numChars == -1) throw new InputMismatchException(); if (curChar >= numChars) { curChar = 0; try { numChars = stream.read(buf); } catch (IOException e) { throw new InputMismatchException(); } if (numChars == -1) return -1; // fin de archivo } return buf[curChar++]; } // para leer líneas enteras, reemplazar c <= ' ' // por una función que compruebe si c es un salto de línea public String next() { int c; do { c = nextByte(); } while (c <= ' '); StringBuilder res = new StringBuilder(); do { res.appendCodePoint(c); c = nextByte(); } while (c > ' '); return res.toString(); } public int nextInt() { // nextLong() se implementaría de forma similar int c; do { c = nextByte(); } while (c <= ' '); int sgn = 1; if (c == '-') { sgn = -1; c = nextByte(); } int res = 0; do { if (c < '0' || c > '9') throw new InputMismatchException(); res = 10 * res + c - '0'; c = nextByte(); } while (c > ' '); return res * sgn; } public double nextDouble() { return Double.parseDouble(next()); } } // EndCodeSnip }
from collections import deque n, m = list(map(int, input().split())) tracks = [input() for _ in range(n)] depth = [[0] * m for _ in range(n)] q = deque([(0, 0)]) depth[0][0] = 1 max_animals = 1 directions = ((-1, 0), (1, 0), (0, -1), (0, 1)) while q: r, c = q.popleft() max_animals = max(max_animals, depth[r][c]) for dr, dc in directions: nr, nc = r + dr, c + dc if 0 <= nr < n and 0 <= nc < m and tracks[nr][nc] != ".": if depth[nr][nc] == 0: if tracks[nr][nc] == tracks[r][c]: depth[nr][nc] = depth[r][c] q.appendleft((nr, nc)) else: depth[nr][nc] = depth[r][c] + 1 q.append((nr, nc)) print(max_animals)

Problemas

HechoFuenteNombreDificultadTagsSolución
CSESLabyrinthFácilBFSSolución
Old SilverPiggybackFácilBFSSolución
CSESMonstersFácilBFSSolución
SilverMilk PailsFácilBFSSolución
CSESGraph GirthNormalCycleSolución
GoldLasers and MirrorsNormalBFSSolución
ACConstruct a PalindromeNormalBFSSolución
GoldCow At LargeNormalBFSSolución
IOI2009 - MechoNormalBFS, Binary SearchSolución
CSESSwap GameNormalBFSSolución
IOIWallsNormalBFSSolución
CFD/D/DNormalSP
ACSmall MultipleDifícilBFSSolución
GoldReplicationDifícilBFSSolución
GoldA Pie for a PieDifícilBFS