• 백준 17144번 미세먼지 안녕! :: 마이구미
    알고리즘 풀이/스택, 큐 2019. 6. 1. 21:29
    반응형
    이 글은 백준 알고리즘 문제 17144번 "미세먼지 안녕!" 을 풀이한다.
    삼성 SW 역량 테스트 문제이다.
    특정 알고리즘을 요구하는 것보다는 정확한 문제 이해를 통한 구현이다.
    문제 링크 - https://www.acmicpc.net/problem/17144

     

    미세먼지를 제거하기 위해 구사과는 공기청정기를 설치하려고 한다. 공기청정기의 성능을 테스트하기 위해 구사과는 집을 크기가 R×C인 격자판으로 나타냈고, 1×1 크기의 칸으로 나눴다. 구사과는 뛰어난 코딩 실력을 이용해 각 칸 (r, c)에 있는 미세먼지의 양을 실시간으로 모니터링하는 시스템을 개발했다. (r, c)는 r행 c열을 의미한다.

    공기청정기는 항상 왼쪽 열에 설치되어 있고, 크기는 두 행을 차지한다. 공기청정기가 설치되어 있지 않은 칸에는 미세먼지가 있고, (r, c)에 있는 미세먼지의 양은 Ar,c이다.

    1초 동안 아래 적힌 일이 순서대로 일어난다.

    1. 미세먼지가 확산된다. 확산은 미세먼지가 있는 모든 칸에서 동시에 일어난다.
      • (r, c)에 있는 미세먼지는 인접한 네 방향으로 확산된다.
      • 인접한 방향에 공기청정기가 있거나, 칸이 없으면 그 방향으로는 확산이 일어나지 않는다.
      • 확산되는 양은 Ar,c/5이고 소수점은 버린다.
      • (r, c)에 남은 미세먼지의 양은 Ar,c - (Ar,c/5)×(확산된 방향의 개수) 이다.
    2. 공기청정기가 작동한다.
      • 공기청정기에서는 바람이 나온다.
      • 위쪽 공기청정기의 바람은 반시계방향으로 순환하고, 아래쪽 공기청정기의 바람은 시계방향으로 순환한다.
      • 바람이 불면 미세먼지가 바람의 방향대로 모두 한 칸씩 이동한다.
      • 공기청정기에서 부는 바람은 미세먼지가 없는 바람이고, 공기청정기로 들어간 미세먼지는 모두 정화된다.

    문제는 어렵지 않게 이해할 수 있다.

    문제의 흐름은 다음과 같다.

     

    1. 미세먼지가 확산된다.
    2. 확산되는 과정은 모든 미세먼지가 동시에 인접한 네 방향으로 확산된다.
    3. 확산될 때 확산의 기준이 되는 미세먼지의 양에서 (원래의 양 / 5) 만큼 확산되고, 기준 미세먼지는 확산된 만큼의 양이 줄어든다.
    4. 확산된 이후, 위쪽 공기청정기는 반시계 방향으로, 아래쪽 공기청정기는 시계방향으로 순환한다.
    5. 바람의 방향대로 모두 한칸씩 이동하고, 공기청정기로 미세먼지가 들어가면 모두 정화된다.

     

    위 과정을 T초 만큼 반복하면 된다.

    메인 코드는 다음과 같다.

     

    for (int i = 0; i < T; i++) {
        spread(); // 미세먼지 확산
        circulate(); // 공기청정기 시계 방향
        circulate(); // 공기청정기 반시계 방향
    }

     

    우선 미세먼지 확산을 위해서는 반드시 이해야하는 것은 동시에 확산된다는 것이다.

    우선 다음 그림을 확인해보자.

     

     

    미세먼지가 (0, 0), (0, 1) 에 존재하여, 확산된다고 가정한 예제이다.

    우선 (0, 0) 을 기준으로 확산시킨 후에 (0, 2) 를 확산시킨 모습이다.

    여기서 문제를 발견하지 못했다면, 문제를 해결할 수 없다.

    위와 같은 흐름은 동시에 확산되는 것이 아닌, 차례대로 확산되고 있다.

    그 결과 각 미세먼지의 확산은 모든 미세먼지의 양에 영향을 끼친다.

    즉, (0 , 1) 을 기준으로 확산시킬 경우, 미세먼지의 양은 7이 아닌 원래의 양인 5를 기준으로 해야한다.

     

    문제에서 원하는 것은 동시에 확산되는 것이기 때문에, 서로 영향을 주면 안된다. 

    이 흐름을 위해서는 문제에서 원하는 것과 같이, 동시에 확산시켜줘야한다.

    이를 위해 단순히, 우리는 큐를 사용하여 모든 미세먼지를 넣은 후, 차례대로 빼면서 미세먼지를 확산시켜주면 된다.

     

    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
    void spread() {
        Queue<Dust> q = new LinkedList<Dust>();
     
        for (int i = 0; i < R; i++) {
            for (int j = 0; j < C; j++) {
                if (map[i][j] > 4) {
                    q.add(new Dust(i, j, map[i][j]));
                }
            }
        }
     
        while (!q.isEmpty()) {
            Dust dust = q.poll();
            int give = dust.n / 5;
            int cnt = 0;
     
            for (int k = 0; k < 4; k++) {
                int nx = dust.c + dx[k];
                int ny = dust.r + dy[k];
     
                if (<= nx && nx < C && <= ny && ny < R) {
                    if (map[ny][nx] != -1) {
                        // 현재 위치에 공기청정기가 없을 경우
                        cnt++;
                        map[ny][nx] += give;
                    }
                }
            }
            map[dust.r][dust.c] -= give * cnt;
        }
    };
    cs


    확산 이후에는 공기청정기 동작을 통해 순환시켜주면된다.

    이 과정은 크게 어려울 거 없이 원본 배열과 복사 배열을 가지고 방향대로 한칸씩 이동해주면 된다.

     

    사실 이 문제는 크게 어렵지 않다.

    하지만 본인은 이 순환 과정에 대한 코드를 작성하기 위해 시간이 조금 필요했다.

    본인뿐만 아니라 다른 사람들도 아마 이것을 조금 더 효율적으로 짜고 싶은 고민이 했을거라 생각한다.

     

    일반적으로 네방향을 처리하기 위해 다음과 같은 방식을 사용한다.

     

    // 위 아래 오른쪽 왼쪽
    int[] dx = { 0, 0, 1, -1 };
    int[] dy = { -1, 1, 0, 0 };

    for(int k = 0; k < 4; k++) {
        int nextX = x + dx[k];
        int nextY = y + dx[y];
    }

     

    하지만 문제에서는 시계 방향과 반시계 방향까지 고려되었다.

    다른 조치를 하지 않는다면, 결국 시계 방향과 반시계 방향을 하드코딩해주어야한다.

    이를 위해 다음과 같은 배열을 만들어줄 수 있다.

     

    int[] ccw = { 2, 0, 3, 1 }; // 반시계(counter-clockwise)
    int[] cw = { 2, 1, 3, 0 }; // 시계(clockwise)

    for (int k = 0; k < 4; k++) {
        int nextX = x + dx[ccw[k];
        int nextY = y + dy[ccw[k];
        // int nextX = x + dx[cw[k];
        // int nextY = y + dy[cw[k];
    }

     

    ccw, cw 배열의 값은 dx, dy 배열을 접근하는 인덱스를 나타낸다.

    위와 같은 방식은 시계 방향과 반시계 방향을 둘다 하드 코딩하지 않아도 된다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    void circulate(int cleanerX, int cleanerY, int[] direction) {
        int y = cleanerY;
        int x = cleanerX + 1;
        map[y][x] = 0;
        for (int k = 0; k < 4; k++) {
            while (true) {
                int nx = x + dx[direction[k]];
                int ny = y + dy[direction[k]];
     
                if (!(<= ny && ny < R && <= nx && nx < C)) {
                    break;
                }
     
                if (cleanerY == ny && cleanerX == nx) {
                    break;
                }
     
                map[ny][nx] = copyMap[y][x];
                y = ny;
                x = nx;
            }
        }
    };
    cs

     

    위 함수를 통해 공기청정기의 순환을 처리할 수 있다.

    단순히 이동방향과 순환방향에 따라, 이전 위치를 다음 위치로 이동하는 동작이다.

    이동 전에 map[y][x] = 0 를 하는 작업이 존재한다.

    이건 문제에서 공기 청정기의 작동 원리 중 "바람이 불면 미세먼지가 바람의 방향대로 모두 한 칸씩 이동한다." 이 위함이다.

     

    전체 코드를 통해 이해하면 많은 도움이 될 것이다.

     

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

    반응형

    댓글 2

Designed by Tistory.