4 minute read

문제 탐색하기

  • 상근이가 가지고 있는 평면도에는 문서의 위치가 모두 나타나 있다.
  • 빌딩의 문은 모두 잠겨있기 때문에, 문을 열려면 열쇠가 필요하다.
  • 상근이는 일부 열쇠를 이미 가지고 있고, 일부 열쇠는 빌딩의 바닥에 놓여져 있다.
  • 상근이는 상하좌우로만 이동할 수 있다.

상근이가 훔칠 수 있는 문서의 최대 개수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 테스트 케이스의 개수가 주어진다. 테스트 케이스의 수는 100개를 넘지 않는다.

각 테스트 케이스의 첫째 줄에는 지도의 높이와 너비 h와 w (2 ≤ h, w ≤ 100)가 주어진다. 다음 h개 줄에는 빌딩을 나타내는 w개의 문자가 주어지며, 각 문자는 다음 중 하나이다.

  • ’.’는 빈 공간을 나타낸다.
  • ‘*‘는 벽을 나타내며, 상근이는 벽을 통과할 수 없다.
  • ’$’는 상근이가 훔쳐야하는 문서이다.
  • 알파벳 대문자는 문을 나타낸다.
  • 알파벳 소문자는 열쇠를 나타내며, 그 문자의 대문자인 모든 문을 열 수 있다.
  • 마지막 줄에는 상근이가 이미 가지고 있는 열쇠가 공백없이 주어진다. 만약, 열쇠를 하나도 가지고 있지 않는 경우에는 “0”이 주어진다.

  • 상근이는 처음에는 빌딩의 밖에 있으며, 빌딩 가장자리의 벽이 아닌 곳을 통해 빌딩 안팎을 드나들 수 있다.
  • 각각의 문에 대해서, 그 문을 열 수 있는 열쇠의 개수는 0개, 1개, 또는 그 이상이고, 각각의 열쇠에 대해서, 그 열쇠로 열 수 있는 문의 개수도 0개, 1개, 또는 그 이상이다.
  • 열쇠는 여러 번 사용할 수 있다.

출력

각 테스트 케이스에 대해서, 상근이가 훔칠 수 있는 문서의 최대 개수를 출력한다.

예제 입력

3
5 17
*****************
.............**$*
*B*A*P*C**X*Y*.X.
*y*x*a*p**$*$**$*
*****************
cz
5 11
*.*********
*...*...*x*
*X*.*.*.*.*
*$*...*...*
***********
0
7 7
*ABCDE*
X.....F
W.$$$.G
V.$$$.H
U.$$$.J
T.....K
*SQPML*
irony

예제 출력

3
1
0

코드 설계하기

구현 방법

  1. BFS를 사용하여 탐색할 수 있는 모든 영역을 탐색한다.
  2. 가장자리에서 시작할 수 있는 모든 위치를 찾아 큐에 넣는다.
  3. 열쇠를 획득하면 해당 열쇠를 사용할 수 있는 문들을 다시 탐색 가능하게 한다.
  4. 문서를 찾을 때마다 카운트를 증가시킨다.
  5. 새로운 열쇠를 찾으면, 이전에 해당 열쇠가 필요한 문 때문에 방문하지 못했던 위치들을 다시 탐색한다.

시간 복잡도

  • 각 칸을 최대 1번만 방문하므로 O(h*w)
  • 열쇠를 새로 발견할 때마다 전체 맵을 다시 확인해야 할 수 있어서, 최악의 경우 O(26hw)
  • 전체 시간 복잡도는 O(h*w)

시도 회차 별 수정사항(Optional)

  • 1차 시도: 초기 BFS 구현. 가장자리 처리 및 일반 BFS 탐색 로직 포함.
  • 2차 시도 (수정): BFS 탐색 중 문서를 발견($)했을 때, documents++만 하고 해당 위치를 ‘.’으로 변경하지 않아 중복 카운트 가능성이 있었음. BFS 내부 로직에서 if (map[nx][ny] == '$') 블록 안에 map[nx][ny] = '.'; 를 추가하여 해결.

문서를 이미 발견한 경우 중복 처리를 고려해야 했다… 🤥

정답 코드 (Java)

package BFS;
import java.io.*;
import java.util.*;

public class b9328 {
    private static int h, w;
    private static char[][] map;
    private static boolean[][] visited;
    private static boolean[] keys;
    private static List<int[]>[] doors;
    private static int[] dx = {-1, 1, 0, 0};
    private static int[] dy = {0, 0, -1, 1};

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        int T = Integer.parseInt(br.readLine());

        while (T-- > 0) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            h = Integer.parseInt(st.nextToken());
            w = Integer.parseInt(st.nextToken());

            map = new char[h][w];
            visited = new boolean[h][w];
            keys = new boolean[26]; // a-z
            doors = new ArrayList[26]; // A-Z

            for (int i = 0; i < 26; i++) {
                doors[i] = new ArrayList<>();
            }

            // 지도 입력
            for (int i = 0; i < h; i++) {
                String line = br.readLine();
                for (int j = 0; j < w; j++) {
                    map[i][j] = line.charAt(j);
                }
            }

            // 초기 열쇠 설정
            String initialKeys = br.readLine();
            if (!initialKeys.equals("0")) {
                for (char key : initialKeys.toCharArray()) {
                    keys[key - 'a'] = true;
                }
            }

            bw.write(bfs() + "\n");
            bw.flush();

        }

        bw.close();

    }

    private static int bfs() {
        Queue<int[]> q = new LinkedList<>();
        int documents = 0;

        // 가장자리 위치를 시작점으로 확인
        for (int i = 0; i < h; i++) {
            for (int j = 0; j < w; j++) {
                if ((i == 0 || i == h - 1 || j == 0 || j == w - 1) && map[i][j] != '*') {
                    if (map[i][j] >= 'A' && map[i][j] <= 'Z') {
                        // 문인 경우 열쇠가 있는지 확인
                        int door = map[i][j] - 'A';
                        if (keys[door]) {
                            q.offer(new int[]{i, j});
                            visited[i][j] = true;
                        } else {
                            doors[door].add(new int[]{i, j});
                        }
                    } else {
                        // 문이 아닌 경우 바로 시작점으로 추가
                        if (map[i][j] == '$') {
                            documents++;
                            map[i][j] = '.';
                        } else if (map[i][j] >= 'a' && map[i][j] <= 'z') {
                            int key = map[i][j] - 'a';
                            keys[key] = true;

                            // 해당 열쇠로 열 수 있는 문들 큐에 추가
                            for (int[] door : doors[key]) {
                                q.offer(door);
                                visited[door[0]][door[1]] = true;
                            }
                            doors[key].clear();
                        }

                        q.offer(new int[]{i, j});
                        visited[i][j] = true;
                    }
                }
            }
        }

        // BFS 탐색
        while (!q.isEmpty()) {
            int[] current = q.poll();
            int x = current[0];
            int y = current[1];

            for (int d = 0; d < 4; d++) {
                int nx = x + dx[d];
                int ny = y + dy[d];

                if (nx >= 0 && nx < h && ny >= 0 && ny < w && !visited[nx][ny] && map[nx][ny] != '*') {
                    if (map[nx][ny] >= 'A' && map[nx][ny] <= 'Z') {
                        // 문인 경우
                        int door = map[nx][ny] - 'A';
                        if (keys[door]) {
                            visited[nx][ny] = true;
                            q.offer(new int[]{nx, ny});
                        } else {
                            doors[door].add(new int[]{nx, ny});
                        }
                    } else {
                        visited[nx][ny] = true;

                        if (map[nx][ny] == '$') {
                            // 문서 발견
                            documents++;
                            map[nx][ny] = '.';
                        } else if (map[nx][ny] >= 'a' && map[nx][ny] <= 'z') {
                            // 열쇠 발견
                            int key = map[nx][ny] - 'a';
                            if (!keys[key]) {
                                keys[key] = true;

                                // 해당 열쇠로 열 수 있는 문들 큐에 추가
                                for (int[] door : doors[key]) {
                                    q.offer(door);
                                    visited[door[0]][door[1]] = true;
                                }
                                doors[key].clear();
                            }
                        }

                        q.offer(new int[]{nx, ny});
                    }
                }
            }
        }

        return documents;
    }
}

Leave a comment