Skip to Content

Monsters

Solución en video

Por David Zhou

Video de YouTube (y4E5g6xkU_0)

Código de la solución en video
#include <algorithm> #include <climits> #include <iostream> #include <queue> #include <string> #include <utility> #include <vector> using namespace std; int n, m; vector<string> maze; vector<vector<int>> dist_monster, dist_person; vector<int> dir_r = {1, 0, -1, 0}, dir_c = {0, 1, 0, -1}; vector<vector<int>> direction; // para reconstruir el camino int closest_dist = INT_MAX, closest_r = -1, closest_c = -1; void bfs(queue<pair<int, int>> &q, vector<vector<int>> &dist) { while (!q.empty()) { int r = q.front().first, c = q.front().second; q.pop(); for (int i = 0; i < 4; i++) { int next_r = r + dir_r[i], next_c = c + dir_c[i]; if (next_r >= 0 && next_r < n && next_c >= 0 && next_c < m && maze[next_r][next_c] != '#' && dist[next_r][next_c] > dist[r][c] + 1) { dist[next_r][next_c] = dist[r][c] + 1; direction[next_r][next_c] = i; q.push({next_r, next_c}); } } } } void check_valid(int r, int c) { if (maze[r][c] != '#' && dist_person[r][c] < dist_monster[r][c] && dist_person[r][c] < closest_dist) { closest_dist = dist_person[r][c]; closest_r = r; closest_c = c; } } int main() { cin >> n >> m; maze.resize(n); dist_monster.resize(n, vector<int>(m, INT_MAX)); dist_person.resize(n, vector<int>(m, INT_MAX)); direction.resize(n, vector<int>(m, -1)); queue<pair<int, int>> q; int person_r, person_c; for (int i = 0; i < n; i++) { cin >> maze[i]; for (int j = 0; j < maze[i].length(); j++) { if (maze[i][j] == 'A') { dist_person[i][j] = 0; person_r = i; person_c = j; } else if (maze[i][j] == 'M') { dist_monster[i][j] = 0; q.push({i, j}); } } } // BFS de los monstruos bfs(q, dist_monster); // BFS de la persona q.push({person_r, person_c}); bfs(q, dist_person); // comprobar celdas del borde for (int i = 0; i < n; i++) { check_valid(i, 0); check_valid(i, m - 1); } for (int j = 0; j < m; j++) { check_valid(0, j); check_valid(n - 1, j); } if (closest_dist == INT_MAX) { cout << "NO" << endl; } else { cout << "YES" << endl << closest_dist << endl; string res = "", convert = "DRUL"; while (closest_r != person_r || closest_c != person_c) { int idx = direction[closest_r][closest_c]; res += convert[idx]; closest_r -= dir_r[idx]; closest_c -= dir_c[idx]; } reverse(res.begin(), res.end()); cout << res << endl; } }
import java.util.*; public class Monsters { static int n, m; static String[] maze; static int[][] distMonster, distPerson, direction; // direction es para reconstruir el camino static int[] dirR = {1, 0, -1, 0}, dirC = {0, 1, 0, -1}; static int closestDist = Integer.MAX_VALUE, closestR = -1, closestC = -1; public static void bfs(Queue<int[]> q, int[][] dist) { while (!q.isEmpty()) { int[] curr = q.poll(); int r = curr[0], c = curr[1]; for (int i = 0; i < 4; i++) { int nextR = r + dirR[i], nextC = c + dirC[i]; if (nextR >= 0 && nextR < n && nextC >= 0 && nextC < m && maze[nextR].charAt(nextC) != '#' && dist[nextR][nextC] > dist[r][c] + 1) { dist[nextR][nextC] = dist[r][c] + 1; direction[nextR][nextC] = i; q.add(new int[] {nextR, nextC}); } } } } public static void checkValid(int r, int c) { if (maze[r].charAt(c) != '#' && distPerson[r][c] < distMonster[r][c] && distPerson[r][c] < closestDist) { closestDist = distPerson[r][c]; closestR = r; closestC = c; } } public static void main(String[] args) { Scanner sc = new Scanner(System.in); n = sc.nextInt(); m = sc.nextInt(); sc.nextLine(); maze = new String[n]; distMonster = new int[n][m]; distPerson = new int[n][m]; direction = new int[n][m]; for (int i = 0; i < n; i++) { maze[i] = sc.nextLine(); Arrays.fill(distMonster[i], Integer.MAX_VALUE); Arrays.fill(distPerson[i], Integer.MAX_VALUE); Arrays.fill(direction[i], -1); } Queue<int[]> q = new LinkedList<>(); int personR = -1, personC = -1; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { char ch = maze[i].charAt(j); if (ch == 'A') { distPerson[i][j] = 0; personR = i; personC = j; } else if (ch == 'M') { distMonster[i][j] = 0; q.add(new int[] {i, j}); } } } // BFS de los monstruos bfs(q, distMonster); // BFS de la persona q.add(new int[] {personR, personC}); bfs(q, distPerson); // comprobar celdas del borde for (int i = 0; i < n; i++) { checkValid(i, 0); checkValid(i, m - 1); } for (int j = 0; j < m; j++) { checkValid(0, j); checkValid(n - 1, j); } if (closestDist == Integer.MAX_VALUE) { System.out.println("NO"); } else { System.out.println("YES"); System.out.println(closestDist); StringBuilder res = new StringBuilder(); String convert = "DRUL"; while (closestR != personR || closestC != personC) { int idx = direction[closestR][closestC]; res.append(convert.charAt(idx)); closestR -= dirR[idx]; closestC -= dirC[idx]; } System.out.println(res.reverse().toString()); } sc.close(); } }
from collections import deque n, m = map(int, input().split()) maze = [input() for _ in range(n)] dist_monster = [[float("inf")] * m for _ in range(n)] dist_person = [[float("inf")] * m for _ in range(n)] direction = [[-1] * m for _ in range(n)] # para reconstruir el camino dir_r = [1, 0, -1, 0] dir_c = [0, 1, 0, -1] convert = "DRUL" closest_dist = float("inf") closest_r, closest_c = -1, -1 def bfs(q, dist): while q: r, c = q.popleft() for i in range(4): next_r, next_c = r + dir_r[i], c + dir_c[i] if ( 0 <= next_r < n and 0 <= next_c < m and maze[next_r][next_c] != "#" and dist[next_r][next_c] > dist[r][c] + 1 ): dist[next_r][next_c] = dist[r][c] + 1 direction[next_r][next_c] = i q.append((next_r, next_c)) def check_valid(r, c): global closest_dist, closest_r, closest_c if ( maze[r][c] != "#" and dist_person[r][c] < dist_monster[r][c] and dist_person[r][c] < closest_dist ): closest_dist = dist_person[r][c] closest_r, closest_c = r, c q = deque() person_r = person_c = -1 for i in range(n): for j in range(m): if maze[i][j] == "A": dist_person[i][j] = 0 person_r, person_c = i, j elif maze[i][j] == "M": dist_monster[i][j] = 0 q.append((i, j)) # BFS de los monstruos bfs(q, dist_monster) # BFS de la persona q = deque() q.append((person_r, person_c)) bfs(q, dist_person) # Comprobar bordes para un escape válido for i in range(n): check_valid(i, 0) check_valid(i, m - 1) for j in range(m): check_valid(0, j) check_valid(n - 1, j) if closest_dist == float("inf"): print("NO") else: print("YES") print(closest_dist) res = [] r, c = closest_r, closest_c while (r, c) != (person_r, person_c): idx = direction[r][c] res.append(convert[idx]) r -= dir_r[idx] c -= dir_c[idx] print("".join(reversed(res)))

Explicación

Como los monstruos se mueven de forma óptima, si un monstruo puede alcanzar una ubicación del laberinto antes que A, entonces A nunca puede moverse a ese lugar. Así, para que A entre a un lugar, la distancia de esa ubicación a A debe ser menor que la distancia de esa ubicación al monstruo más cercano. Sabiendo esto, podemos hacer BFS para hallar todas las ubicaciones que A puede visitar. Esto corre en tiempo NMNM porque cada ubicación se visitará una cantidad constante de veces.

Implementación

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

#include <algorithm> #include <climits> #include <cstring> #include <iostream> #include <queue> #include <vector> #define pii pair<int, int> #define mn 1005 using namespace std; int N, M; queue<pii> q; int paths[mn][mn]; pii from[mn][mn]; int oo = INT_MAX; pii A; string ans; bool possible = false; void retrace(pii node) { // reconstruir desde el nodo final, agregando la dirección del // nodo previo a un string. Este string quedará // al revés pero se invertirá antes de la salida. pii origin = from[node.first][node.second]; if (origin == pii(0, 0)) return; if (origin.first == node.first + 1) ans.push_back('U'); if (origin.first == node.first - 1) ans.push_back('D'); if (origin.second == node.second + 1) ans.push_back('L'); if (origin.second == node.second - 1) ans.push_back('R'); retrace(origin); } void check(pii origin, pii dest) { // comprobar si se puede viajar al destino considerado int pl = paths[origin.first][origin.second]; if (pl + 1 < paths[dest.first][dest.second]) { paths[dest.first][dest.second] = pl + 1; q.push(dest); from[dest.first][dest.second] = origin; } } bool mora = false; // false si bfs de monstruos, true si bfs de A void bfs() { while (!q.empty()) { pii loc = q.front(), next; q.pop(); next = loc; next.first++; check(loc, next); // recorrer ubicaciones adyacentes next = loc; next.first--; check(loc, next); next = loc; next.second++; check(loc, next); next = loc; next.second--; check(loc, next); if (mora && (loc.first == 1 || loc.second == 1 || loc.first == N || loc.second == M)) { cout << "YES" << endl; cout << paths[loc.first][loc.second] << endl; retrace(loc); possible = true; return; } } } int main() { cin >> N >> M; for (int i = 1; i <= N; i++) { string s; cin >> s; for (int j = 1; j <= M; j++) { paths[i][j] = oo; if (s[j - 1] == '#') paths[i][j] = 0; if (s[j - 1] == 'M') { q.push(pii(i, j)); paths[i][j] = 0; } if (s[j - 1] == 'A') { A.first = i; A.second = j; } } } bfs(); // bfs de monstruos mora = true; // cambiar el siguiente bfs al bfs de A from[A.first][A.second] = pii(0, 0); // dar a retrace una ubicación de terminación paths[A.first][A.second] = 0; q.push(A); // preparar el siguiente bfs bfs(); // bfs con A if (possible) { reverse(ans.begin(), ans.end()); cout << ans << endl; } else cout << "NO" << endl; }
import java.io.*; import java.util.*; public class monsters { public static int[] dX = {1, -1, 0, 0}; public static int[] dY = {0, 0, 1, -1}; public static String dirs = "DURL"; public static int N, M; public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); N = Integer.parseInt(st.nextToken()); M = Integer.parseInt(st.nextToken()); char[][] grid = new char[N][M]; for (int i = 0; i < N; i++) { grid[i] = br.readLine().toCharArray(); } int[][] dist = new int[N][M]; // Grilla de distancias de los monstruos. boolean[][] visited = new boolean[N][M]; // Grilla de visitados de los monstruos. Queue<point> q = new LinkedList<>(); // Procesar la grilla. point start = new point(-1, -1); for (int i = 0; i < N; i++) { for (int j = 0; j < M; j++) { // Agregar cada monstruo a la cola. if (grid[i][j] == 'M') { q.add(new point(i, j)); dist[i][j] = 0; visited[i][j] = true; } if (grid[i][j] == 'A') { start = new point(i, j); } } } // Ejecutar un BFS para los monstruos. while (!q.isEmpty()) { point cur = q.poll(); int x = cur.x; int y = cur.y; for (int i = 0; i < 4; i++) { // La siguiente ubicación. int x1 = x + dX[i]; int y1 = y + dY[i]; if (onGrid(x1, y1) && !visited[x1][y1] && grid[x1][y1] != '#') { // Marcar la ubicación como visitada. visited[x1][y1] = true; dist[x1][y1] = dist[x][y] + 1; q.add(new point(x1, y1)); } } } // Inicializar para el BFS humano. q = new LinkedList<>(); q.add(new point(start.x, start.y)); int[][] dist1 = new int[N][M]; // Grilla de distancias del humano. dist1[start.x][start.y] = 0; boolean[][] visited1 = new boolean[N][M]; // Grilla de visitados del // humano. visited1[start.x][start.y] = true; // step[i][j] es la dirección del paso que tomamos para alcanzar el punto (i, j). char[][] step = new char[N][M]; // prevLoc[i][j] es el punto previo antes de alcanzar el punto (i, j). point[][] prevLoc = new point[N][M]; prevLoc[start.x][start.y] = new point(-1, -1); // Ejecutar un BFS para el humano. while (!q.isEmpty()) { point cur = q.poll(); int x = cur.x; int y = cur.y; for (int i = 0; i < 4; i++) { // La siguiente ubicación. int x1 = x + dX[i]; int y1 = y + dY[i]; char d = dirs.charAt(i); // Se alcanzó una casilla del borde. if (!onGrid(x1, y1)) { System.out.println("YES"); System.out.println(dist1[x][y]); StringBuilder ans = new StringBuilder(); // Ir hacia atrás para imprimir todos los pasos. while (x != -1) { if (prevLoc[x][y].x != -1) { ans.append(step[x][y]); } int tmp = x; x = prevLoc[x][y].x; y = prevLoc[tmp][y].y; } System.out.println(ans.reverse()); return; } if (!visited1[x1][y1] && grid[x1][y1] != '#') { if (visited[x1][y1] && dist[x1][y1] <= dist1[x][y] + 1) { continue; } // Marcar la ubicación como visitada. visited1[x1][y1] = true; dist1[x1][y1] = dist1[x][y] + 1; q.add(new point(x1, y1)); prevLoc[x1][y1] = new point(x, y); step[x1][y1] = d; } } } System.out.println("NO"); } // Si el punto está o no en la grilla. public static boolean onGrid(int x, int y) { return (x >= 0 && x < N && y >= 0 && y < M); } public static class point { public int x, y; public point(int x, int y) { this.x = x; this.y = y; } } }
from collections import deque n, m = map(int, input().split()) maze = [input() for _ in range(n)] # grillas de distancia que llevan la cuenta de los pasos mínimos desde monstruos y persona respectivamente dist_monster = [[float("inf")] * m for _ in range(n)] dist_person = [[float("inf")] * m for _ in range(n)] # direction[r][c] guarda qué movimiento se usó para alcanzar (r, c) # se necesita después para reconstruir el camino de escape yendo hacia atrás direction = [[-1] * m for _ in range(n)] # deltas de movimiento y sus etiquetas de letra dir_r = [1, 0, -1, 0] dir_c = [0, 1, 0, -1] convert = "DRUL" # Down, Right, Up, Left # mejor celda del borde que la persona puede alcanzar antes que cualquier monstruo closest_dist = float("inf") closest_r, closest_c = -1, -1 def bfs(q, dist): # BFS multi-fuente, llena dist con las distancias más cortas desde todas las fuentes en q while q: r, c = q.popleft() for i in range(4): next_r, next_c = r + dir_r[i], c + dir_c[i] if ( 0 <= next_r < n and 0 <= next_c < m and maze[next_r][next_c] != "#" # saltar paredes and dist[next_r][next_c] > dist[r][c] + 1 # solo actualizar si es más corto ): dist[next_r][next_c] = dist[r][c] + 1 direction[next_r][next_c] = i # registrar el movimiento para reconstruir el camino q.append((next_r, next_c)) def check_valid(r, c): # una celda del borde es un escape válido si la persona llega estrictamente antes que cualquier monstruo global closest_dist, closest_r, closest_c if ( maze[r][c] != "#" and dist_person[r][c] < dist_monster[r][c] # la persona gana la carrera and dist_person[r][c] < closest_dist # llevar la cuenta del escape más corto ): closest_dist = dist_person[r][c] closest_r, closest_c = r, c q = deque() person_r = person_c = -1 for i in range(n): for j in range(m): if maze[i][j] == "A": dist_person[i][j] = 0 person_r, person_c = i, j elif maze[i][j] == "M": dist_monster[i][j] = 0 # cada monstruo es su propia fuente de BFS q.append((i, j)) # flood fill desde todos los monstruos a la vez bfs(q, dist_monster) # flood fill desde la persona # la grilla direction solo se escribe aquí, así que el BFS de monstruos de arriba la saltea a propósito q = deque([(person_r, person_c)]) bfs(q, dist_person) # comprobar cada celda del borde para un escape válido for i in range(n): check_valid(i, 0) check_valid(i, m - 1) for j in range(m): check_valid(0, j) check_valid(n - 1, j) if closest_dist == float("inf"): print("NO") else: print("YES") print(closest_dist) # ir hacia atrás desde la celda de escape hasta el inicio usando la grilla direction, y luego invertir res = [] r, c = closest_r, closest_c while (r, c) != (person_r, person_c): idx = direction[r][c] res.append(convert[idx]) r -= dir_r[idx] c -= dir_c[idx] print("".join(reversed(res)))