www.acmicpc.net/problem/15684

 

15684번: 사다리 조작

사다리 게임은 N개의 세로선과 M개의 가로선으로 이루어져 있다. 인접한 세로선 사이에는 가로선을 놓을 수 있는데, 각각의 세로선마다 가로선을 놓을 수 있는 위치의 개수는 H이고, 모든 세로선

www.acmicpc.net

문제

사다리 게임은 N개의 세로선과 M개의 가로선으로 이루어져 있다. 인접한 세로선 사이에는 가로선을 놓을 수 있는데, 각각의 세로선마다 가로선을 놓을 수 있는 위치의 개수는 H이고, 모든 세로선이 같은 위치를 갖는다. 아래 그림은 N = 5, H = 6 인 경우의 그림이고, 가로선은 없다.

초록선은 세로선을 나타내고, 초록선과 점선이 교차하는 점은 가로선을 놓을 수 있는 점이다. 가로선은 인접한 두 세로선을 연결해야 한다. 단, 두 가로선이 연속하거나 서로 접하면 안 된다. 또, 가로선은 점선 위에 있어야 한다.

위의 그림에는 가로선이 총 5개 있다. 가로선은 위의 그림과 같이 인접한 두 세로선을 연결해야 하고, 가로선을 놓을 수 있는 위치를 연결해야 한다.

사다리 게임은 각각의 세로선마다 게임을 진행하고, 세로선의 가장 위에서부터 아래 방향으로 내려가야 한다. 이때, 가로선을 만나면 가로선을 이용해 옆 세로선으로 이동한 다음, 이동한 세로선에서 아래 방향으로 이동해야 한다.

위의 그림에서 1번은 3번으로, 2번은 2번으로, 3번은 5번으로, 4번은 1번으로, 5번은 4번으로 도착하게 된다. 

 

입력

사다리에 가로선을 추가해서, 사다리 게임의 결과를 조작하려고 한다. 이때, i번 세로선의 결과가 i번이 나와야 한다. 그렇게 하기 위해서 추가해야 하는 가로선 개수의 최솟값을 구하는 프로그램을 작성하시오.

첫째 줄에 세로선의 개수 N, 가로선의 개수 M, 세로선마다 가로선을 놓을 수 있는 위치의 개수 H가 주어진다. (2 ≤ N ≤ 10, 1 ≤ H ≤ 30, 0 ≤ M ≤ (N-1)×H)

둘째 줄부터 M개의 줄에는 가로선의 정보가 한 줄에 하나씩 주어진다.

가로선의 정보는 두 정수 a과 b로 나타낸다. (1 ≤ a ≤ H, 1 ≤ b ≤ N-1) b번 세로선과 b+1번 세로선을 a번 점선 위치에서 연결했다는 의미이다.

가장 위에 있는 점선의 번호는 1번이고, 아래로 내려갈 때마다 1이 증가한다. 세로선은 가장 왼쪽에 있는 것의 번호가 1번이고, 오른쪽으로 갈 때마다 1이 증가한다.

입력으로 주어지는 가로선이 서로 연속하는 경우는 없다.

출력

i번 세로선의 결과가 i번이 나오도록 사다리 게임을 조작하려면, 추가해야 하는 가로선 개수의 최솟값을 출력한다. 만약, 정답이 3보다 큰 값이면 -1을 출력한다. 또, 불가능한 경우에도 -1을 출력한다.

예제 입력 1

2 0 3

예제 출력 1

0

예제 입력 2

2 1 3

1 1

예제 출력 2

1

예제 입력 3

5 5 6

1 1

3 2

2 3

5 1

5 4

예제 출력 3

3

예제 입력 4

6 5 6

1 1

3 2

1 3

2 5

5 5

예제 출력 4

3

예제 입력 5

5 8 6

1 1

2 2

3 3

4 4

3 1

4 2

5 3

6 4

예제 출력 5

-1

예제 입력 6

5 12 6

1 1

1 3

2 2

2 4

3 1

3 3

4 2

4 4

5 1

5 3

6 2

6 4

예제 출력 6

-1

예제 입력 7

5 6 6

1 1

3 1

5 2

4 3

2 3

1 4

예제 출력 7

2

 

 

 

 

 

 

풀이 1. (틀린 코드 - 시간 초과)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;

    static int n, h, m, ans = -1;
    static int[][] map = null;
    static boolean[][] check = null;

    // 위는 검사하지 않는다. 양 옆에 1 있는지 먼저 체크. 없으면 아래로
    static int[] rArr = {0, 0};  // 좌 우
    static int[] cArr = {-1 ,1};  // 좌 우

    public static boolean isPossible() {
        // i열이 i열로 도작하는지 체크
        // 여기서 결국 탐색 한 번 다 돌려야 할 듯?
        for(int i = 1; i <= n; i++) {
            check = new boolean[h+2][n+1];  // 가로선 계속 타는 무한루프 막기 위한 체킹
            check[1][i] = true;
            int endCol = search(1, i);
            if(endCol != i) {
                return false;
            }
        }
        return true;
    }

    public static int search(int r, int c) {
        // 내가 1이면 내 오른쪽 열로 이동
        // 내 왼쪽이 1이면 내 왼쪽으로 이동
        // 내 아래 행으로는 항상 이동 가능 (우선순위는 열 이동에 있음)

        // 가로선은 H행이 끝이지만 완료 검사는 H+1행에서 해야 한다
        if(r == h+1) {  // 끝에 도착했으면 시작과 동일한지 체크
            int endCol = c;
            return endCol;
        }
        for(int i = 0; i < 2; i++) {  // 좌우 검사
            int nr = r + rArr[i];
            int nc = c + cArr[i];
            if(0 < nr && nr <= h && 0 < nc && nc <= n && !check[nr][nc]) {  // H행 N열 범위 체크
                if(i == 0 && map[nr][nc] == 1) {  // 좌
                    check[nr][nc] = true;
                    check[nr+1][nc] = true;  // 어차피 연속으로 열 이동 불가. 열 이동한 이후엔 어차피 아래로 가야함
                    return search(nr+1, nc);
                }
                if(i == 1 && map[r][c] == 1) {  // 우
                    check[nr][nc] = true;
                    check[nr+1][nc] = true;
                    return search(nr+1, nc);
                }

            }
        }

        check[r+1][c] = true;
        return search(r+1, c);  // 하
    }

    public static void dfs(int r, int c, int deptNow, int dept) {
        if(isPossible()){
            if(ans == -1) {
                ans = deptNow;
            }else {
                ans = Math.min(ans, deptNow);
            }
            return;
        }
        if(deptNow == dept) {
            return;
        }

        // 이제 여기서 가로실선을 하나씩 놓자
        for(int i = r; i <= h; i++) {  // 이전 행은 다시 확인할 필요 없음
            for(int j = 1; j < n; j++) {  // 마지막 열에는 놓을 수 없다
                // 0열은 사용하지 않는 열이고 n열은 애초에 1이 올 수 없는 열이므로 범위 문제 없음
                if(map[i][j] == 0 && map[i][j-1] == 0 && map[i][j+1] == 0) {
                    map[i][j] = 1;
                    dfs(i, j, deptNow + 1, dept);
                    map[i][j] = 0;
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());  // N, M, H 순 입력

        // N : 세로 실선 (2 ~ 20)
        // H : 가로 점선 (1 ~ 30)
        // M : 가로 실선
        // H행 N열
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        h = Integer.parseInt(st.nextToken());
        map = new int[h+2][n+1];
        for(int i = 0; i < m; i++) {
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            map[a][b] = 1;
        }

        dfs(1, 1, 0, m);
        System.out.println(ans > 3 ? -1 : ans);
    }
}

 

시간 초과 코드이지만 일단 예제 입력은 전부 잘 돌아간다.

이걸 짜는 것만 해도 2시간이 걸렸다;

 

로직은 이렇다.

 

백트래킹을 하면서 가로 선을 하나씩 놓는다. 

가로선의 최소 개수를 구하는 것이므로 하나 놓을 때마다 맵을 탐색하여 검사한다. 

isPossible()에서는 각 열을 시작점으로 하여 n번의 탐색을 실시해야 한다. (도중에 하나라도 false가 뜨면 바로 리턴 한다)

 

값이 1인 좌표는 해당 좌표의 오른쪽 열로 넘어갈 수 있다는 뜻.

그러므로 탐색을 할 때는 내 좌표가 1인지, 내 왼쪽 좌표가 1인지를 모두 검사해야 한다. 오->왼 이동도 가능하기 때문.

연속된 가로선은 있을 수 없다는 조건이기 때문에 가로 이동을 실시하는 경우 바로 그 아래칸까지 보내버려도 된다.,

가로 이동이 불가할 경우 세로 이동만 실시.

 

주의할 점은 도착 검사를 h행이 아닌 h+1 행에서 해야 한다는 것이다. h행에서도 다른 열로 이동할 수도 있기 때문이다.

 

백트래킹하며 가로선을 하나씩 놓는 부분에서 시간  초과를 막기 위해 재귀 후 다음 탐색은 r행부터 실시하도록 했지만 시간 초과를 막는 데는 역부족이었다. 결국 시간 초과..

 

 

 

 

 

풀이 2. (정답 코드 - 3276ms)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;

    static int n, h, m, ans = -1;
    static int[][] map = null;
    static boolean[][] check = null;
    static boolean complete;

    // 위는 검사하지 않는다. 양 옆에 1 있는지 먼저 체크. 없으면 아래로
    static int[] rArr = {0, 0};  // 좌 우
    static int[] cArr = {-1 ,1};  // 좌 우

    public static boolean isPossible() {
        // i열이 i열로 도작하는지 체크
        // 여기서 결국 탐색 한 번 다 돌려야 할 듯?
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= h+1; j++) {
                Arrays.fill(check[j], false);  // 가로선 계속 타는 무한루프 막기 위한 체킹
            }
            check[1][i] = true;
            int endCol = search(1, i);
            if(endCol != i) {
                return false;
            }
        }
        return true;
    }

    public static int search(int r, int c) {
        // 내가 1이면 내 오른쪽 열로 이동
        // 내 왼쪽이 1이면 내 왼쪽으로 이동
        // 내 아래 행으로는 항상 이동 가능 (우선순위는 열 이동에 있음)

        // 가로선은 H행이 끝이지만 완료 검사는 H+1행에서 해야 한다
        if(r == h+1) {  // 끝에 도착했으면 시작과 동일한지 체크
            int endCol = c;
            return endCol;
        }
        for(int i = 0; i < 2; i++) {  // 좌우 검사
            int nr = r + rArr[i];
            int nc = c + cArr[i];
            if(0 < nr && nr <= h && 0 < nc && nc <= n && !check[nr][nc]) {  // H행 N열 범위 체크
                if(i == 0 && map[nr][nc] == 1) {  // 좌
                    check[nr][nc] = true;
                    check[nr+1][nc] = true;  // 어차피 연속으로 열 이동 불가. 열 이동한 이후엔 어차피 아래로 가야함
                    return search(nr+1, nc);
                }
                if(i == 1 && map[r][c] == 1) {  // 우
                    check[nr][nc] = true;
                    check[nr+1][nc] = true;
                    return search(nr+1, nc);
                }

            }
        }
        check[r+1][c] = true;
        return search(r+1, c);  // 하
    }

    public static void dfs(int r, int c, int deptNow, int dept) {
        if(complete) return;
        if(deptNow == dept) {
            if(isPossible()) {
                ans = deptNow;  // dept는 1~3 순서로 올라가므로 굳이 대소비교 하지 않고 바로 넣어도 된다
                complete = true;
            }
            return;
        }

        // 이제 여기서 가로실선을 하나씩 놓자
        for(int i = r; i <= h; i++) {  // 이전 행은 다시 확인할 필요 없음
            for(int j = 1; j < n; j++) {  // 마지막 열에는 놓을 수 없다
                // 0열은 사용하지 않는 열이고 n열은 애초에 1이 올 수 없는 열이므로 범위 문제 없음
                if(map[i][j] == 0 && map[i][j-1] == 0 && map[i][j+1] == 0) {
                    map[i][j] = 1;
                    dfs(i, j, deptNow + 1, dept);
                    map[i][j] = 0;
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());  // N, M, H 순 입력

        // N : 세로 실선 (2 ~ 20)
        // H : 가로 점선 (1 ~ 30)
        // M : 가로 실선
        // H행 N열
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        h = Integer.parseInt(st.nextToken());
        map = new int[h+2][n+1];
        check = new boolean[h+2][n+1];
        for(int i = 0; i < m; i++) {
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            map[a][b] = 1;
        }

        for(int i = 0; i <= 3; i++) {
            dfs(1, 1, 0, i);
            if(complete)
                break;
        }
        System.out.println(ans);
    }
}

 

무작정 가로선 하나를 놓을 때마다 검사를 한 번 씩 하는 게 문제였다.

 

목표 dept를 1~3으로 두고 목표 dept에 달성할 때까지는 그냥 놓기만 한 다음 다 놓고서 검사를 하는 방식으로 고쳤다.

정답이 3보다 클 경우 -1을 출력해야하므로 dept는 3까지만 해보면 된다. 

 

처음으로 나오는 정답이 최소값이기 때문에 그 이후엔 재귀나 반복을 더 들어가지 않고 탈출하도록 boolean complete를 사용했다.

 

통과하긴 했지만 아직도 너무 느리다. (3276ms)

 

 

 

 

 

 

 

풀이 3. (정답 코드 - 428ms)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;

    static int n, h, m, ans = -1;
    static int[][] map = null;
    static boolean complete;

    public static boolean isPossible() {
        for(int i = 1; i <= n; i++) {  // 모든 열 검사
            int r = 1;
            int c = i;
            for(int j = 1; j <= h; j++) {
                if(map[r][c-1] == 1) {  // 왼쪽 이동 가능?
                    c -= 1;
                    r += 1;
                    continue;
                }
                if(map[r][c] == 1) {  // 오른쪽 이동 가능?
                    c += 1;
                    r += 1;
                    continue;
                }
                r += 1;  // 양 옆 이동 불가면 아래로 이동

            }
            if(c != i) return false;
        }
        return true;
    }

    public static void dfs(int r, int c, int deptNow, int dept) {
        if(complete) return;
        if(deptNow == dept) {
            if(isPossible()) {
                ans = deptNow;  // dept는 1~3 순서로 올라가므로 굳이 대소비교 하지 않고 바로 넣어도 된다
                complete = true;
            }
            return;
        }

        // 이제 여기서 가로실선을 하나씩 놓자
        for(int i = r; i <= h; i++) {  // 이전 행은 다시 확인할 필요 없음
            for(int j = 1; j < n; j++) {  // 마지막 열에는 놓을 수 없다
                // 0열은 사용하지 않는 열이고 n열은 애초에 1이 올 수 없는 열이므로 범위 문제 없음
                if(map[i][j] == 0 && map[i][j-1] == 0 && map[i][j+1] == 0) {
                    map[i][j] = 1;
                    dfs(i, j, deptNow + 1, dept);
                    map[i][j] = 0;
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());  // N, M, H 순 입력

        // N : 세로 실선 (2 ~ 20)
        // H : 가로 점선 (1 ~ 30)
        // M : 가로 실선
        // H행 N열
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        h = Integer.parseInt(st.nextToken());
        map = new int[h+2][n+1];
        for(int i = 0; i < m; i++) {
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            map[a][b] = 1;
        }

        for(int i = 0; i <= 3; i++) {
            dfs(1, 1, 0, i);
            if(complete)
                break;
        }
        System.out.println(ans);
    }
}

 

시간이 확 줄어들었다.

 

가로선을 다 놓은 후 검사하는 방식에 문제가 있었다.

검사 코드를 너무 DFS 식으로 짜버려서 생긴 문제였다.

 

굳이 재귀와 check배열을 사용해가며 탐색할 필요 없다.

그냥 간단한 인덱스 조작을 통해 빠르게 검사할 수 있었다.

매번 check배열을 초기화하는 부분도 너무 시간낭비였는데 check 자체가 필요없어지니 속도도 빨라졌다.

 

 

 

 

 

여기까지 3시간 반이 걸렸네... 미치겠다...

 

 

 

 

www.acmicpc.net/problem/1937

 

1937번: 욕심쟁이 판다

n*n의 크기의 대나무 숲이 있다. 욕심쟁이 판다는 어떤 지역에서 대나무를 먹기 시작한다. 그리고 그 곳의 대나무를 다 먹어 치우면 상, 하, 좌, 우 중 한 곳으로 이동을 한다. 그리고 또 그곳에서

www.acmicpc.net

문제

n*n의 크기의 대나무 숲이 있다. 욕심쟁이 판다는 어떤 지역에서 대나무를 먹기 시작한다. 그리고 그 곳의 대나무를 다 먹어 치우면 상, 하, 좌, 우 중 한 곳으로 이동을 한다. 그리고 또 그곳에서 대나무를 먹는다. 그런데 단 조건이 있다. 이 판다는 매우 욕심이 많아서 대나무를 먹고 자리를 옮기면 그 옮긴 지역에 그 전 지역보다 대나무가 많이 있어야 한다. 만약에 그런 지점이 없으면 이 판다는 불만을 가지고 단식 투쟁을 하다가 죽게 된다(-_-)

이 판다의 사육사는 이런 판다를 대나무 숲에 풀어 놓아야 하는데, 어떤 지점에 처음에 풀어 놓아야 하고, 어떤 곳으로 이동을 시켜야 둘 다 소중한 생명이지만 판다가 최대한 오래 살 수 있는지 고민에 빠져 있다. 우리의 임무는 이 사육사를 도와주는 것이다. n*n 크기의 대나무 숲이 주어져 있을 때, 이 판다가 최대한 오래 살려면 어떤 경로를 통하여 움직여야 하는지 구하여라.

입력

첫째 줄에 대나무 숲의 크기 n(1 ≤ n ≤ 500)이 주어진다. 그리고 둘째 줄부터 n+1번째 줄까지 대나무 숲의 정보가 주어진다. 대나무 숲의 정보는 공백을 사이로 두고 각 지역의 대나무의 양이 정수 값으로 주어진다. 대나무의 양은 1,000,000보다 작거나 같은 자연수이다.

출력

첫째 줄에는 판다가 최대한 살 수 있는 일수(K)를 출력한다.

예제 입력 1

4

14 9 12 10

1 11 5 4

7 15 2 13

6 3 16 8

예제 출력 1

4

 

 

 

 

 

 

 

풀이 1. (틀린 코드 - 메모리 초과)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static int n, ans;

    static int[][] map = null;
    static int[][][] dp = null;  // dp[r][c][dept] = (r, c)에 dept번째로 방문했을 때 가질 수 있는 최장경로
    static int[] rArr = {-1, 1, 0, 0};
    static int[] cArr = {0, 0, -1 ,1};

    public static void dfs(int r, int c, int dept) {
        dp[r][c][dept] = 0;
        for(int i = 0; i < 4; i++) {
            int nr = r + rArr[i];
            int nc = c + cArr[i];
            if(-1 < nr && nr < n && -1 < nc && nc < n && map[r][c] < map[nr][nc]) {
                if(dp[nr][nc][dept+1] == -1) {
                    dfs(nr, nc, dept+1);
                }
                dp[r][c][dept] = Math.max(dp[r][c][dept], dp[nr][nc][dept+1]);  // 상하좌우 중 최대값
            }
        }
        dp[r][c][dept] += 1;  // 자기 자신에 대한 값 추가
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());
        map = new int[n][n];
        dp = new int[n][n][n*n];
        for(int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for(int j = 0; j < n; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        // dp 초기화
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                Arrays.fill(dp[i][j], -1);
            }
        }

        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                dfs(i, j, 0);
                ans = Math.max(ans, dp[i][j][0]);
            }
        }
        System.out.println(ans);
    }
}


로직은 맞지만 메모리 초과가 나는 코드.

 

매번 풀기 전에 시간, 용량 계산부터 해보자고 다짐하면서도 그걸 실행에 옮기기가 쉽지 않다.

n제한이 dp를 3차원 배열로 사용할 경우 500^4 칸의 int 배열이 필요한데 이는 약 250GB의 용량.. 메모리 제한 256MB를 넘어가도 한참을 넘어간다.

 

로직은 이랬다.

 

dp[r][c][dept] = (r, c)를 dept번째로 방문했을 때 더 나아갈 수 있는 최장경로.

(r, c)에서 상하좌우의 (nr, nc)에 대해 dp[nr][nc][dept+1]의 값 중 최대값을 더한 후 자신에 대한 값 1을 추가하는 것이다.

 

아래 링크의 문자판 문제와 비슷한 방식으로 떠올린 풀이였다.

codeung.tistory.com/216?category=449370  

 

로직은 맞긴 하지만 어쨌든 틀린 코드..

 

3차원 배열의 구조를 유지하면서 dp의 용량을 줄일 수 있는 방법을 고민해보았지만 그런 건 없다.

2차원 배열로 다시 풀어야 한다.

 

 

 

 

 

 

풀이 2. (정답 코드)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static int n, ans;

    static int[][] map = null;
    static int[][] dp = null;  // dp[r][c] = (r, c)를 시작점으로 하여 나아갈 수 있는 최장경로
    static int[] rArr = {-1, 1, 0, 0};
    static int[] cArr = {0, 0, -1 ,1};

    public static void dfs(int r, int c) {
        dp[r][c] = 0;
        for(int i = 0; i < 4; i++) {
            int nr = r + rArr[i];
            int nc = c + cArr[i];
            if(-1 < nr && nr < n && -1 < nc && nc < n && map[r][c] < map[nr][nc]) {
                if(dp[nr][nc] == -1) {
                    dfs(nr, nc);
                }
                dp[r][c] = Math.max(dp[r][c], dp[nr][nc]);  // 상하좌우 중 최대값
            }
        }
        dp[r][c] += 1;  // 자기 자신에 대한 값 추가
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());
        map = new int[n][n];
        dp = new int[n][n];
        for(int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for(int j = 0; j < n; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        // dp 초기화
        for(int i = 0; i < n; i++) {
            Arrays.fill(dp[i], -1);
        }

        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                dfs(i, j);
                ans = Math.max(ans, dp[i][j]);
            }
        }
        System.out.println(ans);
    }
}

 

3차원 배열이 dept부분만 없애서 간단하게 해결했다.

 

3차원 배열은 메모리 초과, 2차원으로 처리해야 한다 -> 2차원으로 줄여도 좌표에 대한 정보는 반드시 포함해야 한다 -> dp[r][c] 에다 뭘 담지? -> (r, c)을 시작점으로 나아갈 수 있는 최장경로

 

굳이 해당 좌표에 몇 번째로 방문하는지 순서까지 메모이제이션 할 필요가 없었다.

어차피 (r, c)에서의 최장경로 = max(상하좌우의 각 (nr, nc)에서의 최장경로) + 1 이니까.

앞선 메모리 초과 코드에서 dp[r][c][0]을 dp[r][c]로 사용한다는 것.

 

 

 

 

www.acmicpc.net/problem/17069

 

17069번: 파이프 옮기기 2

유현이가 새 집으로 이사했다. 새 집의 크기는 N×N의 격자판으로 나타낼 수 있고, 1×1크기의 정사각형 칸으로 나누어져 있다. 각각의 칸은 (r, c)로 나타낼 수 있다. 여기서 r은 행의 번호, c는 열의

www.acmicpc.net

문제

유현이가 새 집으로 이사했다. 새 집의 크기는 N×N의 격자판으로 나타낼 수 있고, 1×1크기의 정사각형 칸으로 나누어져 있다. 각각의 칸은 (r, c)로 나타낼 수 있다. 여기서 r은 행의 번호, c는 열의 번호이고, 행과 열의 번호는 1부터 시작한다. 각각의 칸은 빈 칸이거나 벽이다.

오늘은 집 수리를 위해서 파이프 하나를 옮기려고 한다. 파이프는 아래와 같은 형태이고, 2개의 연속된 칸을 차지하는 크기이다.

파이프는 회전시킬 수 있으며, 아래와 같이 3가지 방향이 가능하다.

파이프는 매우 무겁기 때문에, 유현이는 파이프를 밀어서 이동시키려고 한다. 벽에는 새로운 벽지를 발랐기 때문에, 파이프가 벽을 긁으면 안 된다. 즉, 파이프는 항상 빈 칸만 차지해야 한다.

파이프를 밀 수 있는 방향은 총 3가지가 있으며, →, ↘, ↓ 방향이다. 파이프는 밀면서 회전시킬 수 있다. 회전은 45도만 회전시킬 수 있으며, 미는 방향은 오른쪽, 아래, 또는 오른쪽 아래 대각선 방향이어야 한다.

파이프가 가로로 놓여진 경우에 가능한 이동 방법은 총 2가지, 세로로 놓여진 경우에는 2가지, 대각선 방향으로 놓여진 경우에는 3가지가 있다.

아래 그림은 파이프가 놓여진 방향에 따라서 이동할 수 있는 방법을 모두 나타낸 것이고, 꼭 빈 칸이어야 하는 곳은 색으로 표시되어져 있다.

가로

세로

대각선

가장 처음에 파이프는 (1, 1)와 (1, 2)를 차지하고 있고, 방향은 가로이다. 파이프의 한쪽 끝을 (N, N)로 이동시키는 방법의 개수를 구해보자.

입력

첫째 줄에 집의 크기 N(3 ≤ N ≤ 32)이 주어진다. 둘째 줄부터 N개의 줄에는 집의 상태가 주어진다. 빈 칸은 0, 벽은 1로 주어진다. (1, 1)과 (1, 2)는 항상 빈 칸이다.

출력

첫째 줄에 파이프의 한쪽 끝을 (N, N)으로 이동시키는 방법의 수를 출력한다. 이동시킬 수 없는 경우에는 0을 출력한다.

예제 입력 1

3

0 0 0

0 0 0

0 0 0

예제 출력 1

1

예제 입력 2

4

0 0 0 0

0 0 0 0

0 0 0 0

0 0 0 0

예제 출력 2

3

예제 입력 3

5

0 0 1 0 0

0 0 0 0 0

0 0 0 0 0

0 0 0 0 0

0 0 0 0 0

예제 출력 3

0

예제 입력 4

6

0 0 0 0 0 0

0 1 0 0 0 0

0 0 0 0 0 0

0 0 0 0 0 0

0 0 0 0 0 0

0 0 0 0 0 0

예제 출력 4

13

예제 입력 5

22

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

예제 출력 5

4345413252

 

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        int[][] map = new int[n][n];
        long[][][] dp = new long[n][n][3];  // 0=가로, 1=세로, 2=대각

        for(int i = 0; i < n; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            for(int j = 0; j < n; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        dp[0][1][0] = 1;  // 최초 상태
        for(int i = 0; i < n; i++) {
            for(int j = 2; j < n; j++) {
                if(j - 1 >= 0 && map[i][j] == 0) {  // 가로
                    dp[i][j][0] = dp[i][j-1][0] + dp[i][j-1][2];
                }
                if(i - 1 >= 0 && map[i][j] == 0) {  // 세로
                    dp[i][j][1] = dp[i-1][j][1] + dp[i-1][j][2];
                }
                if(i-1 >= 0 && j-1 >= 0 && map[i][j] == 0 && map[i-1][j] == 0 && map[i][j-1] == 0) {  // 대각
                    dp[i][j][2] = dp[i-1][j-1][0] + dp[i-1][j-1][1] + dp[i-1][j-1][2];
                }
            }
        }

        long ans = dp[n-1][n-1][0] + dp[n-1][n-1][1] + dp[n-1][n-1][2];
        System.out.println(ans);
    }
}

 

codeung.tistory.com/248 이 문제와 똑같은 문제.

n 제한만 커졌다.

 

마지막 예제 출력에서 int로 하면 터진다는 걸 알 수 있다.

 

long으로 바꿔주면 잘 돌아간다.

 

 

 

 

www.acmicpc.net/problem/17070

 

17070번: 파이프 옮기기 1

유현이가 새 집으로 이사했다. 새 집의 크기는 N×N의 격자판으로 나타낼 수 있고, 1×1크기의 정사각형 칸으로 나누어져 있다. 각각의 칸은 (r, c)로 나타낼 수 있다. 여기서 r은 행의 번호, c는 열의

www.acmicpc.net

문제

유현이가 새 집으로 이사했다. 새 집의 크기는 N×N의 격자판으로 나타낼 수 있고, 1×1크기의 정사각형 칸으로 나누어져 있다. 각각의 칸은 (r, c)로 나타낼 수 있다. 여기서 r은 행의 번호, c는 열의 번호이고, 행과 열의 번호는 1부터 시작한다. 각각의 칸은 빈 칸이거나 벽이다.

오늘은 집 수리를 위해서 파이프 하나를 옮기려고 한다. 파이프는 아래와 같은 형태이고, 2개의 연속된 칸을 차지하는 크기이다.

파이프는 회전시킬 수 있으며, 아래와 같이 3가지 방향이 가능하다.

파이프는 매우 무겁기 때문에, 유현이는 파이프를 밀어서 이동시키려고 한다. 벽에는 새로운 벽지를 발랐기 때문에, 파이프가 벽을 긁으면 안 된다. 즉, 파이프는 항상 빈 칸만 차지해야 한다.

파이프를 밀 수 있는 방향은 총 3가지가 있으며, →, ↘, ↓ 방향이다. 파이프는 밀면서 회전시킬 수 있다. 회전은 45도만 회전시킬 수 있으며, 미는 방향은 오른쪽, 아래, 또는 오른쪽 아래 대각선 방향이어야 한다.

파이프가 가로로 놓여진 경우에 가능한 이동 방법은 총 2가지, 세로로 놓여진 경우에는 2가지, 대각선 방향으로 놓여진 경우에는 3가지가 있다.

아래 그림은 파이프가 놓여진 방향에 따라서 이동할 수 있는 방법을 모두 나타낸 것이고, 꼭 빈 칸이어야 하는 곳은 색으로 표시되어져 있다.

가로

세로

대각선

가장 처음에 파이프는 (1, 1)와 (1, 2)를 차지하고 있고, 방향은 가로이다. 파이프의 한쪽 끝을 (N, N)로 이동시키는 방법의 개수를 구해보자.

입력

첫째 줄에 집의 크기 N(3 ≤ N ≤ 16)이 주어진다. 둘째 줄부터 N개의 줄에는 집의 상태가 주어진다. 빈 칸은 0, 벽은 1로 주어진다. (1, 1)과 (1, 2)는 항상 빈 칸이다.

출력

첫째 줄에 파이프의 한쪽 끝을 (N, N)으로 이동시키는 방법의 수를 출력한다. 이동시킬 수 없는 경우에는 0을 출력한다. 방법의 수는 항상 1,000,000보다 작거나 같다.

예제 입력 1

3

0 0 0

0 0 0

0 0 0

예제 출력 1

1

예제 입력 2

4

0 0 0 0

0 0 0 0

0 0 0 0

0 0 0 0

예제 출력 2

3

예제 입력 3

5

0 0 1 0 0

0 0 0 0 0

0 0 0 0 0

0 0 0 0 0

0 0 0 0 0

예제 출력 3

0

예제 입력 4

6

0 0 0 0 0 0

0 1 0 0 0 0

0 0 0 0 0 0

0 0 0 0 0 0

0 0 0 0 0 0

0 0 0 0 0 0

예제 출력 4

13

 

 

 

 

 

풀이 1. (DP)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        int[][] map = new int[n][n];
        int[][][] dp = new int[n][n][3];  // 0=가로, 1=세로, 2=대각

        for(int i = 0; i < n; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            for(int j = 0; j < n; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        dp[0][1][0] = 1;  // 최초 상태
        for(int i = 0; i < n; i++) {
            for(int j = 2; j < n; j++) {
                if(j - 1 >= 0 && map[i][j] == 0) {  // 가로
                    dp[i][j][0] = dp[i][j-1][0] + dp[i][j-1][2];
                }
                if(i - 1 >= 0 && map[i][j] == 0) {  // 세로
                    dp[i][j][1] = dp[i-1][j][1] + dp[i-1][j][2];
                }
                if(i-1 >= 0 && j-1 >= 0 && map[i][j] == 0 && map[i-1][j] == 0 && map[i][j-1] == 0) {  // 대각
                    dp[i][j][2] = dp[i-1][j-1][0] + dp[i-1][j-1][1] + dp[i-1][j-1][2];
                }
            }
        }

        int ans = dp[n-1][n-1][0] + dp[n-1][n-1][1] + dp[n-1][n-1][2];
        System.out.println(ans);
    }
}

 

디피디피디피디피디피 이놈의 디피..

 

처음 문제를 봤을 때는 예제 그림 때문에 새로운 파이프를 하나씩 놓는 것으로 착각하였다.

45도만 회전이 가능하다는 조건도 조금 모호한 부분이 있지 않나 싶다.  

 

DP 문제는 언제나 어떤 값을 메모이제이션 할 것인지가 가장 중요하다.

dp[r][c][stat] = 파이프 (r, c)을 끝점으로 하며 stat 상태로 존재할 수 있는 경우의 수

stat : 가로=0, 세로=1, 대각=2

 

각 방향으로 파이프를 옮길 수 있는 경우는 아래와 같다.

 

1. 가로 방향으로 파이프를 놓음  

-> 왼쪽 칸에서 가로, 대각일 때만 가능

 

2. 세로 방향으로 파이프를 놓음

-> 위쪽 칸에서 세로, 대각일 때만 가능

 

3. 대각 방향으로 파이프를 놓음

-> 왼쪽 위 칸에서 가로, 세로, 대각일 때 모두 가능

 

 

 

 

 

풀이 2. (DFS or BFS)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static int n, ans;

    static int[][] map = null;
    static int[] rArr = {-1, 1, 0, 0};
    static int[] cArr = {0, 0, -1 ,1};

    public static void dfs(int r, int c, int dir) {
        if(r == n-1 && c == n-1) {
            ans += 1;
            return;
        }

        if(dir == 0) {  // 가로, 대각 이동 가능
            if(c+1 < n && map[r][c+1] == 0) {
                dfs(r, c+1, 0);
            }
            if(r+1 < n && c+1 < n && map[r+1][c] == 0 && map[r][c+1] == 0 && map[r+1][c+1] == 0) {
                dfs(r+1, c+1, 2);
            }
        }else if(dir == 1) {  // 세로, 대각 이동 가능
            if(r+1 < n && map[r+1][c] == 0) {
                dfs(r+1, c, 1);
            }
            if(r+1 < n && c+1 < n && map[r+1][c] == 0 && map[r][c+1] == 0 && map[r+1][c+1] == 0) {
                dfs(r+1, c+1, 2);
            }
        }else {  // 가로, 세로, 대각 이동 가능
            if(c+1 < n && map[r][c+1] == 0) {
                dfs(r, c+1, 0);
            }
            if(r+1 < n && map[r+1][c] == 0) {
                dfs(r+1, c, 1);
            }
            if(r+1 < n && c+1 < n && map[r+1][c] == 0 && map[r][c+1] == 0 && map[r+1][c+1] == 0) {
                dfs(r+1, c+1, 2);
            }
        }
    }


    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());
        map = new int[n][n];
        for(int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for(int j = 0; j < n; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        dfs(0, 1, 0);
        System.out.println(ans);
    }
}

 

사실 처음에 DFS로 짰다가 틀려서 정답을 확인하고 DP로 짰던 건데.. DFS로도 짤 수 있었다.

 

습관적으로 check 배열을 사용했는데 이 부분이 문제였다.

나름대로 머리를 쓴다고 3차원 배열을 사용해서 각 상태마다 해당 좌표에서 존재한 적이 있는지 따로 검사를 했었는데.. 

 

그냥 검사 자체를 하면 안 된다.

어떤 좌표에 어떤 상태로 존재한 적이 있었다고 해도 그 좌표까지 도달하기 위한 경로가 다를 수 있기 때문이다.

 

그 부분만 고쳐줬더니 바로 통과되었다.

 

 

 

탐색 조건 부분에서 DP와 DFS의 차이는, DP는 현재 위치로 오기 전 위치에 어떤 상태로 존재할 수 있는지를 검사하는 것이고 DFS는 현재 위치에서 다음 위치로 어떤 상태로 이동할 수 있는지를 검사하는 것이다.

www.acmicpc.net/problem/16946

 

16946번: 벽 부수고 이동하기 4

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 한 칸에서 다른 칸으로 이동하려면, 두 칸이 인접해야 한다. 두 칸이

www.acmicpc.net

문제

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 한 칸에서 다른 칸으로 이동하려면, 두 칸이 인접해야 한다. 두 칸이 변을 공유할 때, 인접하다고 한다.

각각의 벽에 대해서 다음을 구해보려고 한다.

  • 벽을 부수고 이동할 수 있는 곳으로 변경한다.
  • 그 위치에서 이동할 수 있는 칸의 개수를 세어본다.

한 칸에서 이동할 수 있는 칸은 상하좌우로 인접한 칸이다.

입력

첫째 줄에 N(1 ≤ N ≤ 1,000), M(1 ≤ M ≤ 1,000)이 주어진다. 다음 N개의 줄에 M개의 숫자로 맵이 주어진다.

출력

맵의 형태로 정답을 출력한다. 원래 빈 칸인 곳은 0을 출력하고, 벽인 곳은 이동할 수 있는 칸의 개수를 10으로 나눈 나머지를 출력한다.

예제 입력 1

3 3 101 010 101

예제 출력 1

303 050 303

예제 입력 2

4 5

11001

00111

01010

10101

예제 출력 2

46003

00732

06040

50403

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;


class Pair {
    int r, c;
    Pair(int r, int c) {
        this.r = r;
        this.c = c;
    }
}

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static StringBuilder sb = null;

    static int[][] map = null;
    static int[][] group = null;
    static int[] cntOfGroup = null;
    static boolean[][] check = null;

    static int N, M, dept, groupNum = 1;
    static int[] rArr = {-1, 1, 0, 0};
    static int[] cArr = {0, 0, -1, 1};

    public static void dfs(int r, int c) {
        for(int i = 0; i < 4; i++) {
            int nr = r + rArr[i];
            int nc = c + cArr[i];
            if(-1 < nr && nr < N && -1 < nc && nc < M) {
                if(!check[nr][nc] && map[nr][nc] == 0) {
                    check[nr][nc] = true;
                    group[nr][nc] = groupNum;
                    dept += 1;
                    dfs(nr, nc);
                }
            }
        }
    }

    public static void bfs(int r, int c) {
        Queue<Pair> que = new ArrayDeque<>();
        que.add(new Pair(r, c));

        while(!que.isEmpty()) {
            Pair p = que.poll();
            for(int i = 0; i < 4; i++) {
                int nr = p.r + rArr[i];
                int nc = p.c + cArr[i];
                if(-1 < nr && nr < N && -1 < nc && nc < M) {
                    if(!check[nr][nc] && map[nr][nc] == 0) {
                        check[nr][nc] = true;
                        group[nr][nc] = groupNum;
                        dept += 1;
                        que.add(new Pair(nr, nc));
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());

        map = new int[N][M];
        group = new int[N][M];
        check = new boolean[N][M];
        cntOfGroup = new int[N * M + 1];
        for(int i = 0; i < N; i++) {
            String str = br.readLine();
            for(int j = 0; j < M; j++) {
                map[i][j] = str.charAt(j) - '0';
            }
        }

        // 탐색 진행하며 이동 가능 경로들 그룹핑. 각 그룹의 원소의 개수 저장.
        for(int i = 0; i < N; i++) {
            for(int j = 0; j < M; j++) {
                if(!check[i][j] && map[i][j] == 0) {
                    check[i][j] = true;
                    group[i][j] = groupNum;
                    dept = 1;
//                    dfs(i, j);
                    bfs(i, j);
                    cntOfGroup[groupNum] = dept;
                    groupNum += 1;
                }
            }
        }

        sb = new StringBuilder();
        for(int i = 0; i < N; i++) {
            for(int j = 0; j < M; j++) {
                if(map[i][j] == 0) {
                    sb.append("0");
                }else {  // map[i][j] == 1
                    // 상하좌우 검사하여 인접한 그룹의 그룹번호 모두 담는다
                    Set<Integer> set = new HashSet<>();
                    for(int k = 0; k < 4; k++) {
                        int nr = i + rArr[k];
                        int nc = j + cArr[k];
                        if(-1 < nr && nr < N && -1 < nc && nc < M) {
                            if(group[nr][nc] != 0 && !set.contains(group[nr][nc])) {
                                set.add(group[nr][nc]);
                            }
                        }
                    }

                    int sum = 1;  // 자기 칸까지 포함해야하므로 1로 초기화
                    for(int num : set) {  // 인접한 그룹의 cnt 모두 더함
                        sum += cntOfGroup[num];
                    }
                    sb.append(String.valueOf(sum % 10));
                }
            }
            sb.append("\n");
        }
        System.out.println(sb.toString());
    }
}

 

벽이 있는 모든 좌표에 대해서 1 -> 0으로 바꾸고 DFS or BFS를 돌려서 하나하나 값을 구하는 방식이 먼저 떠올랐다.

시간초과가 날 것 같은 무식한 방법..

 

결국 구글링으로 해법을 확인했다. 키 포인트는 분리 집합이다.

map을 이루는 이동 가능한 덩어리를 그룹핑하고 각 그룹에 대한 좌표의 개수를 구한다. 

그 후 1인 좌표에 대해서 자신과 인접한 그룹들의 카운트를 더한 값을 출력한다.

 

알고나면 그렇게까지 어려운 해법은 아닌데 전혀 떠올리지 못했다.

이런 게 디테일이고 실력이겠지.

 

 

 

 

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

문제

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로로 이동하려 한다. 최단경로는 맵에서 가장 적은 개수의 칸을 지나는 경로를 말하는데, 이때 시작하는 칸과 끝나는 칸도 포함해서 센다.

만약에 이동하는 도중에 벽을 부수고 이동하는 것이 좀 더 경로가 짧아진다면, 벽을 K개 까지 부수고 이동하여도 된다.

한 칸에서 이동할 수 있는 칸은 상하좌우로 인접한 칸이다.

맵이 주어졌을 때, 최단 경로를 구해 내는 프로그램을 작성하시오.

입력

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

출력

첫째 줄에 최단 거리를 출력한다. 불가능할 때는 -1을 출력한다.

예제 입력 1

6 4 1

0100

1110

1000

0000

0111

0000

예제 출력 1

15

예제 입력 2

6 4 2

0100

1110

1000

0000

0111

0000

예제 출력 2

9

예제 입력 3

4 4 3

0111

1111

1111

1110

예제 출력 3

-1

 

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.StringTokenizer;

class Pair {
    int r, c, d, cnt;
    Pair(int r, int c, int d, int cnt) {
        this.r = r;
        this.c = c;
        this.d = d;
        this.cnt = cnt;
    }
}

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static int[][] map = null;
    static boolean[][][] check = null;
    static int N, M, K, ans = -1;
    static int[] rArr = {-1, 1, 0, 0};
    static int[] cArr = {0, 0, -1, 1};

    public static void bfs() {
        Queue<Pair> que = new ArrayDeque<>();
        que.add(new Pair(0, 0, 0, 1));
        check[0][0][0] = true;

        while(!que.isEmpty()) {
            Pair p = que.poll();
            int r = p.r, c = p.c, d = p.d, cnt = p.cnt;
            if(r == N - 1 && c == M - 1) {
                ans = cnt;
                return;
            }

            for(int i = 0; i < 4; i++) {
                int nr = r + rArr[i];
                int nc = c + cArr[i];
                if(-1 < nr && nr < N && -1 < nc && nc < M) {
                    if(map[nr][nc] == 1) {  // 다음 좌표가 벽
                        if(d < K && !check[nr][nc][d+1]) {  // 아직 벽을 더 뚫을 수 있으면 계속 진행
                            que.add(new Pair(nr, nc, d + 1, cnt + 1));
                            check[nr][nc][d+1] = true;
                        }
                    }else {  // 다음 좌표 뚫려있음
                        if(!check[nr][nc][d]) {
                            que.add(new Pair(nr, nc, d, cnt + 1));
                            check[nr][nc][d] = true;
                        }
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        K = Integer.parseInt(st.nextToken());

        map = new int[N][M];
        check = new boolean[N][M][K+1];
        for(int i = 0; i < N; i++) {
            String str = br.readLine();
            for(int j = 0; j < M; j++) {
                map[i][j] = str.charAt(j) - '0';
            }
        }

        bfs();
        System.out.println(ans);
    }
}

 

codeung.tistory.com/24 에서 K 제한만 추가된 문제.

 

조건문을 조금 수정하여 바로 해결했다.

 

 

 

 

www.acmicpc.net/problem/1726

 

1726번: 로봇

많은 공장에서 로봇이 이용되고 있다. 우리 월드 공장의 로봇은 바라보는 방향으로 궤도를 따라 움직이며, 움직이는 방향은 동, 서, 남, 북 가운데 하나이다. 로봇의 이동을 제어하는 명령어는

www.acmicpc.net

문제

많은 공장에서 로봇이 이용되고 있다. 우리 월드 공장의 로봇은 바라보는 방향으로 궤도를 따라 움직이며, 움직이는 방향은 동, 서, 남, 북 가운데 하나이다. 로봇의 이동을 제어하는 명령어는 다음과 같이 두 가지이다.

  • 명령 1. Go k: k는 1, 2 또는 3일 수 있다. 현재 향하고 있는 방향으로 k칸 만큼 움직인다.
  • 명령 2. Turn dir: dir은 left 또는 right 이며, 각각 왼쪽 또는 오른쪽으로 90° 회전한다.

공장 내 궤도가 설치되어 있는 상태가 아래와 같이 0과 1로 이루어진 직사각형 모양으로 로봇에게 입력된다. 0은 궤도가 깔려 있어 로봇이 갈 수 있는 지점이고, 1은 궤도가 없어 로봇이 갈 수 없는 지점이다. 로봇이 (4, 2) 지점에서 남쪽을 향하고 있을 때,  이 로봇을 (2, 4) 지점에서 동쪽으로 향하도록 이동시키는 것은 아래와 같이 9번의 명령으로 가능하다.

로봇의 현재 위치와 바라보는 방향이 주어졌을 때, 로봇을 원하는 위치로 이동시키고, 원하는 방향으로 바라보도록 하는데 최소 몇 번의 명령이 필요한지 구하는 프로그램을 작성하시오.

입력

첫째 줄에 공장 내 궤도 설치 상태를 나타내는 직사각형의 세로 길이 M과 가로 길이 N이 빈칸을 사이에 두고 주어진다. 이때 M과 N은 둘 다 100이하의 자연수이다. 이어 M줄에 걸쳐 한 줄에 N개씩 각 지점의 궤도 설치 상태를 나타내는 숫자 0 또는 1이 빈칸을 사이에 두고 주어진다. 다음 줄에는 로봇의 출발 지점의 위치 (행과 열의 번호)와 바라보는 방향이 빈칸을 사이에 두고 주어진다. 마지막 줄에는 로봇의 도착 지점의 위치 (행과 열의 번호)와 바라보는 방향이 빈칸을 사이에 두고 주어진다. 방향은 동쪽이 1, 서쪽이 2, 남쪽이 3, 북쪽이 4로 주어진다. 출발지점에서 도착지점까지는 항상 이동이 가능하다.

출력

첫째 줄에 로봇을 도착 지점에 원하는 방향으로 이동시키는데 필요한 최소 명령 횟수를 출력한다.

예제 입력 1

5 6

0 0 0 0 0 0

0 1 1 0 1 0

0 1 0 0 0 0

0 0 1 1 1 0

1 0 0 0 0 0

4 2 3

2 4 1

예제 출력 1

9

 

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.StringTokenizer;

class Pair {
    int r, c, dir, cnt;
    Pair(int r, int c, int dir, int cnt) {
        this.r = r;
        this.c = c;
        this.dir = dir;
        this.cnt = cnt;
    }
}

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static int[][] map = null;
    static boolean[][][] check = null;

    static Pair begin = null;
    static Pair end = null;
    static int N, M, ans;

    // [0, 1, 2, 3, 4] = [0, 동, 서, 남, 북] 방향 맞춰줘야 한다
    static int[] rArr = {0, 0, 0, 1, -1};
    static int[] cArr = {0, 1, -1, 0, 0};

    public static void bfs() {
        Queue<Pair> que = new ArrayDeque<>();
        que.add(begin);
        check[begin.r][begin.c][begin.dir] = true;

        while(!que.isEmpty()) {
            Pair p = que.poll();
            int r = p.r, c = p.c, dir = p.dir, cnt = p.cnt;
            if(r == end.r && c == end.c && dir == end.dir) {
                ans = cnt;
                return;
            }

            // 현재 바라본 방향에서 1, 2, 3칸 이동 명령
            for(int i = 1; i <= 3; i++) {
                int nr = r + rArr[dir] * i;  // 현재 방향으로 i칸 이동
                int nc = c + cArr[dir] * i;
                if(1 <= nr && nr <= N && 1 <= nc && nc <= M) {  // 범위 체크
                    if(map[nr][nc] == 0 && !check[nr][nc][dir]) {
                        que.add(new Pair(nr, nc, dir, cnt + 1));
                        check[nr][nc][dir] = true;
                    }else if(map[nr][nc] == 1) {
                        break;  // 벽을 만나면 더 건너뛸 수 없음
                    }
                }
            }

            // 현재 방향을 제외한 모든 방향으로 이동
            // 180도 방향에 대해서는 +2 처리하고 나머진 +1
            for(int i = 1; i <= 4; i++) {  // i = 이동할 방향
                if(dir != i && !check[r][c][i]) {  // 현재 위치에서 방향만 도는 것임
                    int inst = 1;
                    if(dir == 1) {
                        if(i == 2) inst++;
                    }else if(dir == 2) {
                        if(i == 1) inst++;
                    }else if(dir == 3) {
                        if(i == 4) inst++;
                    }else if(dir == 4){
                        if(i == 3) inst++;
                    }
                    que.add(new Pair(r, c, i, cnt + inst));
                    check[r][c][i] = true;
                }
            }
        }

    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());

        map = new int[N+1][M+1];
        check = new boolean[N+1][M+1][5];
        for(int i = 1; i <= N; i++) {
            st = new StringTokenizer(br.readLine());
            for(int j = 1; j <= M; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        st = new StringTokenizer(br.readLine());
        int r = Integer.parseInt(st.nextToken());
        int c = Integer.parseInt(st.nextToken());
        int dir = Integer.parseInt(st.nextToken());
        begin = new Pair(r, c, dir, 0);

        st = new StringTokenizer(br.readLine());
        r = Integer.parseInt(st.nextToken());
        c = Integer.parseInt(st.nextToken());
        dir = Integer.parseInt(st.nextToken());
        end = new Pair(r, c, dir, 0);

        bfs();
        System.out.println(ans);
    }
}

 

접근법은 어렵지 않게 떠올릴 수 있었는데 구현에서 애를 먹었다.

 

현재 위치에서 수행할 수 있는 모든 명령을 수행해서 큐에 넣으면서 BFS를 돌리면 된다.

수행할 수 있는 명령은 아래와 같다.

1. 앞으로 이동 1 or 2 or 3칸 (3가지)

2. 현재 바라보고 있는 방향을 제외한 너머지 방향으로 방향 전환 (3가지)

이렇게 총 6가지이다.

 

이때 주의해야 할 점은, 방향전환에 사용되는 명령어의 개수가 모두 같지는 않다는 것이다.

한 번의 명령으로 90도만 회전할 수 있기 때문에 180도를 꺾어서 반대 방향을 보기 위해서는 두 번의 카운트를 세야 한다.

 

앞으로 이동하는 건 그냥 세면 되겠다만.. 이놈의 방향전환을 어떻게 해야 할지가 문제였다.

동, 서, 남 북 = 1, 2, 3, 4로 방향을 표시해야 하는데..

한 번 돌 때마다 방향을 1씩 증가or감소 시키고 1, 4에서의 증감은 4, 1로 건너뛰게 하는 식으로 하려고 했는데..

90도씩 전환하는 방향의 순서는 동쪽부터 시작한다고 했을 때 동서남북이 아니라 동남서북 순서가 되어버리기 때문이다.

 

그래서 동서남북 방향 1,2,3,4를 인덱스로 두고 dir = {0, 1, 3, 2, 4} 이렇게 배열의 원소를 가리키도록 짤까 했지만 이것 때문에 코드가 너무 더러워지고 헷갈려서... 결국 구글링으로 정답을 확인했다.

 

 

매번 느끼는 거지만 이런 식으로 구현능력이 부족해서 막히는 경우가 너무 많다.

로직 자체를 떠올리지 못하는 건 아닌데 이런 식으로 막히면 좌절감이 두 배가 되는 듯 하다.

 

 

방향 전환을 90도씩 할 때마다 명령을 한 번 수행한다는 점에 주목하자.

다시말해, 명령 횟수를 세는 기준을 "어느 방향으로 회전했는가?"가 아니라 "몇 도를 회전했는가?"로 잡는 것이다.

 

즉,

목표 방향이 동쪽이라면 이전 방향이 서쪽일 경우에 +2, 나머지는 +1

목표 방향이 서쪽이라면 이전 방향이 동쪽일 경우에 +2, 나머지는 +1

목표 방향이 남쪽이라면 이전 방향이 북쪽일 경우에 +2, 나머지는 +1

목표 방향이 북쪽이라면 이전 방향이 남쪽일 경우에 +2, 나머지는 +1

이렇게 명령 횟수를 더해주는 것이다.

 

 

 

 

 

 

풀이 2.

import java.io.*;
import java.util.*;

class Pair {
    int r, c, d, cnt;
    Pair(int r, int c, int d, int cnt) {
        this.r = r;
        this.c = c;
        this.d = d;
        this.cnt = cnt;
    }
}

public class Main {
    static BufferedReader br = null;
    static BufferedWriter bw = null;
    static StringBuilder sb = null;
    static StringTokenizer st = null;

    static int n, m, ans;
    static int[][] map = null;
    static boolean[][][] check = null;
    static int[] rArr = {0, 0, 0, 1, -1};  // X동서남북 맞춰줘야 함
    static int[] cArr = {0, 1, -1, 0, 0};

    static Pair start = null;
    static Pair end = null;

    public static void bfs() {
        Queue<Pair> que = new ArrayDeque<>();
        que.add(start);
        check[start.r][start.c][start.d] = true;

        while(!que.isEmpty()) {
            Pair p = que.poll();
            int r = p.r, c = p.c, d = p.d, cnt = p.cnt;
            if(r == end.r && c == end.c && d == end.d) {
                ans = cnt;
                return;
            }

            // 내 방향으로 1, 2, 3 이동
            for(int i = 1; i <= 3; i++) {
                int nr = r + rArr[d] * i;
                int nc = c + cArr[d] * i;
                if(1 <= nr && nr <= n && 1 <= nc && nc <= m) {
                    if(map[nr][nc] == 1) break;  // 벽을 만나면 더 나아갈 수 없음
                    if(!check[nr][nc][d]) {
                        check[nr][nc][d] = true;
                        que.add(new Pair(nr, nc, d, cnt+1));
                    }
                }
            }

            // 왼쪽 90도, 오른쪽 90도
            if(d == 1 || d == 2) {
                if(!check[r][c][3]) {
                    check[r][c][3] = true;
                    que.add(new Pair(r, c, 3, cnt+1));
                }
                if(!check[r][c][4]) {
                    check[r][c][4] = true;
                    que.add(new Pair(r, c, 4, cnt+1));
                }
            }else if(d == 3 || d == 4) {
                if(!check[r][c][1]) {
                    check[r][c][1] = true;
                    que.add(new Pair(r, c, 1, cnt+1));
                }
                if(!check[r][c][2]) {
                    check[r][c][2] = true;
                    que.add(new Pair(r, c, 2, cnt+1));
                }
            }
        }
    }

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

        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        map = new int[n+1][m+1];
        check = new boolean[n+1][m+1][5];
        for(int i = 1; i <= n; i++) {
            st = new StringTokenizer(br.readLine());
            for(int j = 1; j <= m; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        st = new StringTokenizer(br.readLine());
        start = new Pair(Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken()), 0);
        st = new StringTokenizer(br.readLine());
        end = new Pair(Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken()), 0);

        bfs();
        System.out.println(ans);
    }
}

 

생각해보니 굳이 모든 방향으로 전부 돌기 위해 애를 쓸 필요가 없었다. 그냥 현재 위치에서 왼쪽, 오른쪽으로 한 번 씩만 돌고 마무리해도 나머지 방향으로는 그 다음 턴에서 돌아줄 테니까.

 

그냥 1 or 2 or 3칸 이동 3가지 경우 + 좌 or 우 90도 2가지 경우만 수행하고 다음 반복으로 넘어가면 된다.

 

 

 

 

www.acmicpc.net/problem/2206

 

2206번: 벽 부수고 이동하기

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

www.acmicpc.net

문제

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로로 이동하려 한다. 최단경로는 맵에서 가장 적은 개수의 칸을 지나는 경로를 말하는데, 이때 시작하는 칸과 끝나는 칸도 포함해서 센다.

만약에 이동하는 도중에 한 개의 벽을 부수고 이동하는 것이 좀 더 경로가 짧아진다면, 벽을 한 개 까지 부수고 이동하여도 된다.

한 칸에서 이동할 수 있는 칸은 상하좌우로 인접한 칸이다.

맵이 주어졌을 때, 최단 경로를 구해 내는 프로그램을 작성하시오.

입력

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

출력

첫째 줄에 최단 거리를 출력한다. 불가능할 때는 -1을 출력한다.

예제 입력 1

6 4

0100

1110

1000

0000

0111

0000

예제 출력 1

15

예제 입력 2

4 4

0111

1111

1111

1110

예제 출력 2

-1

 

 

 

 

 

 

풀이 1. (틀린 코드)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Queue;
import java.util.StringTokenizer;

class Pair {
    int r, c;
    Pair(int r, int c) {
        this.r = r;
        this.c = c;
    }
}

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static int[][] map = null;
    static int[][][] path = null;
    static int N, M;
    static int[] rArr = {-1, 1, 0, 0};
    static int[] cArr = {0, 0, -1, 1};

    public static void bfs() {
        Queue<Pair> que = new ArrayDeque<>();
        que.add(new Pair(0, 0));
        path[0][0][0] = 1;

        while(!que.isEmpty()) {
            Pair p = que.poll();
            int r = p.r;
            int c = p.c;

            for(int i = 0; i < 4; i++) {
                int nr = r + rArr[i];
                int nc = c + cArr[i];

                if(-1 < nr && nr < N && -1 < nc && nc < M) {
                    // 다음 좌표가 0일 때는 이전에 벽을 부쉈든 안 부쉈든 이동 가능
                    // 1. 이전에 벽을 부순 적 없고 이번에도 안 부수고 이동
                    // 2. 이전에 벽을 부순 적 있고 이번에는 안 부수고 이동

                    // 1. 이전에 벽을 부순 적 없고 이번에도 안 부수고 이동
                    if(map[nr][nc] == 0  // 벽 안 부수고 갈 수 있니?
                            && path[nr][nc][0] == N * M  // 벽 안 부수고 방문 안 했던 노드 맞니?
                            && path[r][c][1] == N * M) {  // 지금까지 벽 부순 적 없는 거 맞니?
                        path[nr][nc][0] = path[r][c][0] + 1;
                        que.add(new Pair(nr, nc));
                    }
                    // 2. 이전에 벽을 부순 적 있고 이번에는 안 부수고 이동
                    if(map[nr][nc] == 0  // 벽 안 부수고 갈 수 있니?
                            && path[nr][nc][1] == N * M  // 벽 부수고 방문 안 했던 노드 맞니?
                            && path[r][c][1] != N * M) {  // 지금까지 벽 부순 적 있는 거 맞니?
                        path[nr][nc][1] = path[r][c][1] + 1;
                        que.add(new Pair(nr, nc));
                    }

                    // 다음 좌표가 1일 때는 이전에 벽을 안 부쉈을 경우만 이동 가능
                    // 3. 이전에 벽을 안 부쉈고 이번에 부수면서 이동
                    if(map[nr][nc] == 1  // 벽 부숴야 갈 수 있는 거 맞니?
                            && path[nr][nc][1] == N * M  // 이전에 벽 부수고 방문한 적 없는 거 맞니?
                            && path[r][c][1] == N * M) {  // 지금까지 벽 부순 적 없는 거 맞니?
                        path[nr][nc][1] = path[r][c][0] + 1;  // 이번 턴에서 벽을 부숨
                        que.add(new Pair(nr, nc));
                    }
                }

            }
        }
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());

        map = new int[N][M];
        path = new int[N][M][2];
        for(int i = 0; i < N; i++) {
            String str = br.readLine();
            for(int j = 0; j < M; j++) {
                map[i][j] = str.charAt(j) - '0';
                Arrays.fill(path[i][j], N * M);
            }
        }

        bfs();
        int ans = 0;
        if(path[N-1][M-1][0] == N*M && path[N-1][M-1][1] == N*M) {  // 이동할 수 없는 경우
            ans = -1;
        }else if(path[N-1][M-1][0] < path[N-1][M-1][1]) {
            ans = path[N-1][M-1][0];
        }else {
            ans = path[N-1][M-1][1];
        }
        System.out.println(ans);
    }
}

 

path[r][c][0] = (r, c)에 벽을 한 번도 부수지 않고 이동하는 최단경로

path[r][c][1] = (r, c)에 벽을 한 번만 부수고 이동하는 최단경로

 

이렇게 잡아두고 모든 경로를 계산하려 했다.

 

주어진 예제들에 대해서는 맞게 동작하는데 11%에서 틀려버린다.. 

어디가 틀린 건지 모르겠다.

 

세 가지 경우의 수에 대해서 쓸 데 없는 조건도 하나씩 들어가있는 거 같아서 제거해보았지만 여전히 틀린다.

 

 

 

 

 

풀이 2. (정답 코드)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.StringTokenizer;

class Pair {
    int r, c, cnt, destroy;
    Pair(int r, int c, int cnt, int destroy) {
        this.r = r;
        this.c = c;
        this.cnt = cnt;
        this.destroy = destroy;
    }
}

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static int[][] map = null;
    static boolean[][][] check = null;
    static int N, M, ans = -1;
    static int[] rArr = {-1, 1, 0, 0};
    static int[] cArr = {0, 0, -1, 1};

    public static void bfs() {
        Queue<Pair> que = new ArrayDeque<>();
        que.add(new Pair(0, 0, 1, 0));
        check[0][0][0] = true;

        while(!que.isEmpty()) {
            Pair p = que.poll();
            int r = p.r;
            int c = p.c;
            int cnt = p.cnt;
            int destroy = p.destroy;

            if(r == N - 1 && c == M - 1) {
                ans = cnt;
                return;
            }

            for(int i = 0; i < 4; i++) {
                int nr = r + rArr[i];
                int nc = c + cArr[i];

                if(-1 < nr && nr < N && -1 < nc && nc < M) {
                    if(map[nr][nc] == 1) {  // 벽이면??
                        if(destroy == 0 && !check[nr][nc][1]) {  // 이전에 부순적 없으면 부수고 이동
                            que.add(new Pair(nr, nc, cnt + 1,  destroy + 1));
                            check[nr][nc][destroy+1] = true;
                        }
                    }else{  // 벽이 아니면??
                        if(!check[nr][nc][destroy]) {  // 현재 destroy 횟수로 다음 좌표 갈 수 있는지만
                            que.add(new Pair(nr, nc, cnt + 1, destroy));  // destroy 유지하면서 이동
                            check[nr][nc][destroy] = true;
                        }
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());

        map = new int[N][M];
        check = new boolean[N][M][2];
        for(int i = 0; i < N; i++) {
            String str = br.readLine();
            for(int j = 0; j < M; j++) {
                map[i][j] = str.charAt(j) - '0';
            }
        }
        bfs();
        System.out.println(ans);
    }
}

 

check와 cnt를 하나의 배열로 관리하고 싶어서 path[][][]를 사용했던 건데 오히려 그것 때문에 더 헷갈리게 된 거 같다. 코드도 지저분했고.

 

Pair 안에 cnt와 destroy(지금까지 벽 부순 횟수)를 포함하도록 짰더니 훨씬 깔끔해졌다.

 

 

 

 

+ Recent posts