ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 백준 2206, 14442
    Java/코딩테스트 2023. 8. 13. 22:44

    ------------------------- 사담 -------------------------

    이 문제는 CS 스터디에서 스터디원분이 추천해준 문제이다. 왜 추천해줬었는지 기억은 안 나지만...추천해준 것만 기억하고 일단 풀었다...  

    벽 부수고 이동하기 문제가 시리즈로 3인가 4까지 있었던거 같은데 일단 2까지는 풀었다. 

    처음 2206을 풀고 이해했다면 14442는 변수만 살짝 바꿔서 쉽게 풀수 있을 것이다.


     

    https://www.acmicpc.net/problem/2206

     

    2206번: 벽 부수고 이동하기

    N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로

    www.acmicpc.net

    가장 먼저 떠올릴 수 있는 풀이법은 모든 벽 정보를 다 넣고 하나하나 탐색해보는 완전 탐색... 방식이다.

    하지만 골드 3 난이도.... 그렇게 풀면 당연히 시간초과 나게 만들었다. 

     

    일단 총 칸이 최대 1000 * 1000 이고, 벽의 최대 개수도 1000 * 1000 - 2이다. 

    BFS를 벽 하나 없앨 때마다 돌리는 방식으로는 2%인가 거기서 바로 시간초과 난다 ㅋㅋ 

    따라서... BFS 밖에서 벽을 선택해서 뚫고 돌리는 방식이 아닌, 진행하면서 벽을 뚫고 왔는지 / 안 뚫고 왔는지 케이스별로 구하는 방식으로 진행해야 한다. 

     

    기존 방법대로라면 방문 배열을 2차원 배열로 하고, 마지막 칸 값을 출력했었다. 

    이 방문 배열을 3차원 배열로 int[row][col][2] 로 정의하여 마지막 3번째 값이 0이면 안 뚫은 상태, 1이면 이미 하나 뚫은 상태로 해서 계산하면 된다. 

     

    코드가 설명이 더 빠를 것 같아서 코드로 설명 첨부.

    Pos 클래스에 isBroken이라는 변수를 담았다. 해당 Pos를 큐에서 꺼낼 때 isBroken = true라면 앞 어딘가에서 하나 부수고 진행해왔다는 뜻이 된다. 

    private static class Pos {
            int x;
            int y;
            boolean isBroken;
            public Pos(int x, int y, boolean isBroken) {
                this.x = x;
                this.y = y;
                this.isBroken = isBroken;
            }
    }

     

    만약 지도 상에서 벽을 만났다면, 이전 상태가 벽을 부순 적이 없어야 한다. 

    즉, poll.isBroken 값이 false여야 한다. 

    벽이 아닌 경우라면, 이전에 부쉈든 안 부쉈든 갈 수 있다. 대신 3차원 방문 배열의 3번째 원소값이 달라지므로, 분기를 나눠서 isBroken이 참인 경우와 거짓인 경우로 나눠 계산하고, 큐에 더해줘야 한다. 

    if (map[nextX][nextY] == 1) { // 벽을 만났다
    // 아직 벽을 부순 적이 없고, 방문한 적이 없는 경우에만 지나가기
    	if (!isBroken && visited[nextX][nextY][0] == 0){
    		visited[nextX][nextY][1] = visited[poll.x][poll.y][0] + 1;
    		queue.add(new Pos(nextX, nextY, true));
    	}
    } else {
    	if (isBroken && visited[nextX][nextY][1] == 0) {
    		visited[nextX][nextY][1] = visited[poll.x][poll.y][1] + 1;
    		queue.add(new Pos(nextX, nextY, true));
    	}
    	if (!isBroken && visited[nextX][nextY][0] == 0) {
    		visited[nextX][nextY][0] = visited[poll.x][poll.y][0] + 1;
    		queue.add(new Pos(nextX, nextY, false));
    	}
    }

     

    그리고 마지막에 정답을 산출하는 부분도 주의가 필요하다. 

    단순이 뚫는 경우 안뚫는 경우 중 최솟값으로 정답을 처리하면 아래 예시에서 틀린 답이 출력된다. 

    • 벽이 하나도 없는 경우 (이 경우 visited[row-1][col-1][1] = 0일 것이다. 
    • 벽을 무조건 뚫어야만 진행 가능한 경우 ( visited[row-1][col-1][0] = 0) 

    따라서 3가지 케이스 다 나눠줘야한다.

    벽 뚫고도, 안 뚫고도 도달하는 경우 / 벽 하나도 없는 경우 / 벽을 안 뚫고서는 못 도달하는 경우 

    이렇게 케이스 다 나눠서 답안을 각각 찾아주면 된다. 

    // 위치에 도달했을 때
    if (poll.x == row-1 && poll.y == col-1) {
    	// 벽을 뚫는 방법 & 안 뚫는 방법 모두 도달 가능했다면
    	if (visited[poll.x][poll.y][0] != 0 && visited[poll.x][poll.y][1] != 0) {
    		answer = Math.min(visited[poll.x][poll.y][0], visited[poll.x][poll.y][1]);
    	}
    	// 벽이 하나도 없는 경우
    	else if (visited[poll.x][poll.y][1] == 0){
    		answer = visited[poll.x][poll.y][0];
    	}
    	// 벽을 안 뚫고서는 못 도달하는 경우
    	else if (visited[poll.x][poll.y][0] == 0){
    		answer = visited[poll.x][poll.y][1];
    	}
    	break;
    }

     

    전체 코드는 아래와 같다. 

    answer = -1이므로 아예 도달 못했다면 -1이 출력될 것이다. 

    import java.util.*;
    import java.io.*;
    public class p2206 {
        private static int[] dirX = { -1, 1, 0, 0 };
        private static int[] dirY = { 0, 0, -1, 1 };
        private static int[][] map;
        private static int[][][] visited;
        private static int row, col, answer = -1;
        public static void main(String[] args) throws IOException {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            StringTokenizer st = new StringTokenizer(br.readLine());
            row = Integer.parseInt(st.nextToken());
            col = Integer.parseInt(st.nextToken());
    
            map = new int[row][col];
            visited = new int[row][col][2];
    
            for (int i = 0; i < row; i++) {
                String line = br.readLine();
                for (int j = 0; j < col; j++) {
                    map[i][j] = Integer.parseInt(line.substring(j, j + 1));
                }
            }
    
            BFS();
            System.out.println(answer);
    
        }
    
        private static void BFS() {
            Queue<Pos> queue = new LinkedList<>();
            queue.add(new Pos(0,0 , false));
            visited[0][0][0] = 1;
    
            while (!queue.isEmpty()) {
                Pos poll = queue.poll();
    
                // 위치에 도달했을 때
                if (poll.x == row-1 && poll.y == col-1) {
                    // 벽을 뚫는 방법 & 안 뚫는 방법 모두 도달 가능했다면
                    if (visited[poll.x][poll.y][0] != 0 && visited[poll.x][poll.y][1] != 0) {
                        answer = Math.min(visited[poll.x][poll.y][0], visited[poll.x][poll.y][1]);
                    }
                    // 벽이 하나도 없는 경우
                    else if (visited[poll.x][poll.y][1] == 0){
                        answer = visited[poll.x][poll.y][0];
                    }
                    // 벽을 안 뚫고서는 못 도달하는 경우
                    else if (visited[poll.x][poll.y][0] == 0){
                        answer = visited[poll.x][poll.y][1];
                    }
                    break;
                }
    
                for (int i = 0; i < 4; i++) {
                    int nextX = poll.x + dirX[i];
                    int nextY = poll.y + dirY[i];
                    boolean isBroken = poll.isBroken;
    
                    if (!isValid(nextX, nextY)) {
                        continue;
                    }
    
                    if (map[nextX][nextY] == 1) { // 벽을 만났다
                        // 아직 벽을 부순 적이 없고, 방문한 적이 없는 경우에만 지나가기
                        if (!isBroken && visited[nextX][nextY][0] == 0){
                            visited[nextX][nextY][1] = visited[poll.x][poll.y][0] + 1;
                            queue.add(new Pos(nextX, nextY, true));
                        }
                    } else {
                        if (isBroken && visited[nextX][nextY][1] == 0) {
                            visited[nextX][nextY][1] = visited[poll.x][poll.y][1] + 1;
                            queue.add(new Pos(nextX, nextY, true));
                        }
                        if (!isBroken && visited[nextX][nextY][0] == 0) {
                            visited[nextX][nextY][0] = visited[poll.x][poll.y][0] + 1;
                            queue.add(new Pos(nextX, nextY, false));
                        }
                    }
    
    
                }
            }
    
        }
    
        private static boolean isValid(int nextX, int nextY) {
            if (nextX < 0 || nextX >= row || nextY < 0 || nextY >= col) {
                return false;
            }
            return true;
        }
    
        private static class Pos {
            int x;
            int y;
            boolean isBroken;
            public Pos(int x, int y, boolean isBroken) {
                this.x = x;
                this.y = y;
                this.isBroken = isBroken;
            }
        }
    }

     

     


    https://www.acmicpc.net/problem/14442

     

    14442번: 벽 부수고 이동하기 2

    첫째 줄에 N(1 ≤ N ≤ 1,000), M(1 ≤ M ≤ 1,000), K(1 ≤ K ≤ 10)이 주어진다. 다음 N개의 줄에 M개의 숫자로 맵이 주어진다. (1, 1)과 (N, M)은 항상 0이라고 가정하자.

    www.acmicpc.net

     

    위의 응용버전이지만 별로 차이가 없다. 

    2206번과 달리, 얘는 K개까지 벽을 뚫을 수 있다. 

    따라서 위에선 방문배열 사이즈가 [row][col][2] 였다면, 애는 [row][col][k+1]로 해주면 된다.

    아예 안 뚫는 경우부터 k개 다 뚫은 경우까지 모두를 계산하기 위해서이다. 

     

    얘도 거의 비슷한데 위에서 따로 설명한 큐에 넣는 부분 / 정답 산출하는 부분만 약간 변경하면 된다. 그리고 Pos 클래스에 boolean이 아닌 int 타입으로 부순 횟수를 기록한다. 

    private static class Pos {
            int x;
            int y;
            int broken;
    }

     

    똑같이 벽인 경우와 아닌 경우를 나눈다.

    벽이 아닌 경우는 그냥 방문만 안 한 경우면 다 지나갈 수 있으므로 broken 변수는 그대로 두고, 방문 처리후 큐에 넣어준다. 

    벽인 경우라면 먼저 여태까지 부순 벽의 개수에서 한 번 더 부숴도 괜찮은지 검사 후, 아직 방문하지 않은 경우에만 방문 처리 후 큐에 넣어주면 된다. 

    if (map[nextX][nextY] == 0) {
    	if (visited[nextX][nextY][broken] == 0) {
    		visited[nextX][nextY][broken] = visited[poll.x][poll.y][broken] + 1;
    		queue.add(new Pos(nextX, nextY, broken));
    	}
    
    } else {
    	if (broken + 1 <= k) {
    		if (visited[nextX][nextY][broken+1] == 0) {
    			visited[nextX][nextY][broken+1] =  visited[poll.x][poll.y][broken] + 1;
    			queue.add(new Pos(nextX, nextY, broken+1));
    		}
    	}
    }

    정답 출력 부분은 0~k까지 반복문을 돌려가며 가장 작은 값을 찾으면 된다. 

    이 때 0이라면 해당 개수만큼 뚫었을 땐 도달 못했다는 이야기니까 skip하면 된다.

    if (poll.x == row-1 && poll.y == col-1) {
    	int temp = Integer.MAX_VALUE;
    	for (int i = 0; i <= k; i++) {
    		if (visited[poll.x][poll.y][i] != 0) {
    			temp = Math.min(temp, visited[poll.x][poll.y][i]);
    		}
    	}
    	if (temp != Integer.MAX_VALUE) {
    		answer = temp;
    	}
    	break;
    }

     

    전체 코드는 아래와 같다. 

    import java.util.*;
    import java.io.*;
    public class p14442 {
        private static int row, col, k, answer = -1;
        private static int[][] map;
        private static int[][][] visited;
        private static int[] dirX = { -1, 1, 0, 0 };
        private static int[] dirY = { 0, 0, -1, 1 };
        public static void main(String[] args) throws IOException {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            StringTokenizer st = new StringTokenizer(br.readLine());
    
            row = Integer.parseInt(st.nextToken());
            col = Integer.parseInt(st.nextToken());
            k = Integer.parseInt(st.nextToken());
    
            map = new int[row][col];
            visited = new int[row][col][k+1];
    
            for (int i = 0; i < row; i++) {
                String line = br.readLine();
                for (int j = 0; j < col; j++) {
                    map[i][j] = Integer.parseInt(line.substring(j, j+1));
                }
            }
    
            BFS();
            System.out.println(answer);
        }
    
        private static void BFS() {
            Queue<Pos> queue = new LinkedList<>();
            queue.add(new Pos(0, 0, 0));
            visited[0][0][0] = 1;
    
            while (!queue.isEmpty()) {
                Pos poll = queue.poll();
    
                if (poll.x == row-1 && poll.y == col-1) {
                    int temp = Integer.MAX_VALUE;
                    for (int i = 0; i <= k; i++) {
                        if (visited[poll.x][poll.y][i] != 0) {
                            temp = Math.min(temp, visited[poll.x][poll.y][i]);
                        }
                    }
                    if (temp != Integer.MAX_VALUE) {
                        answer = temp;
                    }
                    break;
                }
    
                for (int i = 0; i < 4; i++) {
                    int nextX = poll.x + dirX[i];
                    int nextY = poll.y + dirY[i];
                    int broken = poll.broken;
    
                    if (!isValid(nextX, nextY)) {
                        continue;
                    }
    
                    if (map[nextX][nextY] == 0) {
                        if (visited[nextX][nextY][broken] == 0) {
                            visited[nextX][nextY][broken] = visited[poll.x][poll.y][broken] + 1;
                            queue.add(new Pos(nextX, nextY, broken));
                        }
    
                    } else {
                        if (broken + 1 <= k) {
                            if (visited[nextX][nextY][broken+1] == 0) {
                                visited[nextX][nextY][broken+1] =  visited[poll.x][poll.y][broken] + 1;
                                queue.add(new Pos(nextX, nextY, broken+1));
                            }
                        }
                    }
                }
            }
        }
    
    
        private static boolean isValid(int nextX, int nextY) {
            if (nextX < 0 || nextX >= row || nextY < 0 || nextY >= col) {
                return false;
            }
            return true;
        }
        private static class Pos {
            int x;
            int y;
            int broken;
    
            public Pos(int x, int y, int broken) {
                this.x = x;
                this.y = y;
                this.broken= broken;
            }
        }
    }

    'Java > 코딩테스트' 카테고리의 다른 글

    백준 2240  (0) 2023.08.15
    백준 2293  (0) 2023.08.15
    백준 2228  (0) 2023.08.10
    백준 11000  (0) 2023.08.08
    프로그래머스 구명보트, 조이스틱  (0) 2023.08.07
Designed by Tistory.