• 백준 14890번 경사로 :: 마이구미
    알고리즘 풀이/수학 2018. 4. 6. 18:41
    반응형

    이 글은 백준 알고리즘 문제 14890번 "경사로" 를 풀이한다.

    접근 방법은 단순 문제 이해를 통한 구현이 된다.

    2017 삼성 SW 역량 테스트 기출문제이다.

    문제 링크 - https://www.acmicpc.net/problem/14890


    오늘은 이 지도에서 지나갈 수 있는 길이 몇 개 있는지 알아보려고 한다. 길이란 한 행 또는 한 열 전부를 나타내며, 한쪽 끝에서 다른쪽 끝까지 지나가는 것이다. 


    길을 지나갈 수 있으려면 길에 속한 모든 칸의 높이가 모두 같아야 한다. 또는, 경사로를 놓아서 지나갈 수 있는 길을 만들 수 있다. 경사로는 높이가 항상 1이며, 길이는 L이다. 또, 개수는 매우 많아 부족할 일이 없다. 경사로는 낮은 칸과 높은 칸을 연결하며, 아래와 같은 조건을 만족해야한다.

    • 경사로는 낮은 칸에 놓으며, L개의 연속된 칸에 경사로의 바닥이 모두 접해야 한다.
    • 낮은 칸과 높은 칸의 높이 차이는 1이어야 한다.
    • 경사로를 놓을 낮은 칸의 높이는 모두 같아야 하고, L개의 칸이 연속되어 있어야 한다.

    아래와 같은 경우에는 경사로를 놓을 수 없다.

    • 경사로를 놓은 곳에 또 경사로를 놓는 경우
    • 낮은 칸과 높은 칸의 높이 차이가 1이 아닌 경우
    • 낮은 지점의 칸의 높이가 모두 같지 않거나, L개가 연속되지 않은 경우
    • 경사로를 놓다가 범위를 벗어나는 경우


    본인은 문제를 한번에 이해하기 힘들었다.

    하지만 완전히 이해하기 어려운 문제가 아니다.

    또한 어려운 알고리즘을 요구하는 것이 아니기에 문제를 꼼꼼히 읽고 풀어보길 추천한다.


    문제의 핵심은 경사로가 필요한 곳의 높이 차이는 1이라는 것과 L 길이만큼 연속되어있다는 것이다.

    다음 그림으로 표현할 수 있다.


    14890번 경사로

    위와 같이 높이 차가 1이고, L 만큼 연속되어있어야 경사로를 배치할 수 있다.

    그렇지 않은 경우는 다음과 같다.


    14890번 경사로

    왼쪽은 높이 차가 1이 아니기 때문에, 경사로를 배치할 수 없다.

    오른쪽은 L만큼 배치할 수 있는 공간이 없기 때문에 경사로를 배치할 수 없게 된다.


    이렇게만 보면, 단순히 연속된 수에 대해 카운터를 쌓은 후, 높은 칸을 발견하면 경사로를 놓을 수 있는지 판단하면 된다.

    그림으로 표현하면 다음과 같다.

    14890번 경사로


    하지만 여기서 문제는 연속된 수를 쌓다가 높은 칸이 아닌 낮은 칸을 만날 수도 있다는 것이다.

    그렇게 되면, 지금까지 지나오면서 쌓은 카운터를 이용할 수 없게 된다.

    즉, 높은 칸을 기준으로 다음 칸들로부터 경사로를 배치할 수 있는 지 판단해야한다.


    14890번 경사로


    크게 높은 칸을 만났을 때와 낮은 칸을 만났을 때를 구분한다.

    큰 흐름은 다음과 같다.


    1. 길을 지나오면서 연속된 수에 대해서는 카운터를 증가시킨다.
    2. 연속되지 않는 수를 만난다면, 높은 칸인지 낮은 칸인지 확인한다.
    3. 높은 칸이라면, 쌓아온 카운터를 통해 경사로를 배치할 수 있는 지 확인한다.
    4. 낮은 칸이라면, L만큼 지나가면서 경사로를 배치할 수 있는 지 연속 유무를 확인한다.


    Github - https://github.com/hotehrud/acmicpc/blob/master/SAMSUMG/14890.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    static int[][] road;
    static int[] height;
    static int n, l;
     
    private void solve() {
        n = sc.nextInt();
        l = sc.nextInt();
        road = new int[n * 2][n];
        int ans = n * 2;
     
        for (int i = 0; i < n; i++) {
            String[] input = sc.readLine().split(" ");
            for (int j = 0; j < n; j++) {
                road[i][j] = Integer.parseInt(input[j]);
            }
        }
     
        for (int i = n; i < n * 2; i++) {
            for (int j = 0; j < n; j++) {
                road[i][j] = road[j][i - n];
            }
        }
     
        // 높은 칸을 만났을 경우 => 지나온 길을 통해 경사로 배치 판단 가능.
        // 낮은 칸을 만났을 경우 => 낮은 칸부터 길을 지나가면서 경사로를 배치할 수 있는 지 판단해야함.
     
        for (int i = 0; i < n * 2; i++) {
            int target = road[i][0];
            height = new int[11];
            height[target] = 1;
            int j = 1;
            while (j < n) {
                int next = road[i][j];
                if (!vaildHeight(target, next)) {
                    // 높이 차가 1보다 크면 경사로 배치 불가능
                    --ans;
                    break;
                }
                if (target != next) {
                    if (target < next) {
                        // 높은 칸 만났을 경우
                        if (!high(target, next)) {
                            --ans;
                            break;
                        }
                    } else {
                        // 낮은 칸 만났을 경우
                        if (!low(i, j, target, next)) {
                            --ans;
                            break;
                        }
                        j += l - 1;
                    }
                    // 기준이 되는 칸 갱신
                    target = next;
                } else {
                    // 같은 높이의 칸을 뜻하기 때문에, L의 길이를 갖는 경사로 배치 판단을 위한 카운터 증가
                    height[target]++;
                }
                j++;
            }
        }
        System.out.println(ans);
    }
     
    public static boolean low(int i, int j, int target, int next) {
        // 길이가 L인 경사로를 놓을 수 있는지만 판단하면 되기에 l 만큼 돌린다.
        for (int k = 0; k < l; k++) {
            if (j + k == n) {
                break;
            }
            if (road[i][j + k] == next) {
                height[next]++;
            }
        }
        if (height[next] < l) {
            return false;
        }
        height[next] -= l;
        return true;
    }
     
    public static boolean high(int target, int next) {
        if (height[target] < l) {
            return false;
        }
        // 다음을 위해 카운터 배열 갱신
        height[target] = 0;
        height[next] = 1;
        return true;
    }
     
    public static boolean vaildHeight(int a, int b) {
        int gap = Math.abs(a - b);
        if (gap > 1) {
            return false;
        }
        return true;
    }    
    cs


    반응형

    댓글

Designed by Tistory.