← All Articles

[BAEKJOON] 2931. 가스관

Posted on

문제 :

러시아 가스를 크로아티아로 운반하기 위해 자그레브와 모스코바는 파이프라인을 디자인하고 있다. 두 사람은 실제 디자인을 하기 전에 파이프 매니아 게임을 이용해서 설계를 해보려고 한다.

이 게임에서 유럽은 R행 C열로 나누어져 있다. 각 칸은 비어있거나, 아래 그림과 같은 일곱가지 기본 블록으로 이루어져 있다.

image

가스는 모스크바에서 자그레브로 흐른다. 가스는 블록을 통해 양방향으로 흐를 수 있다. ’+‘는 특별한 블록으로, 아래 예시처럼 두 방향 (수직, 수평)으로 흘러야 한다.

image

파이프 라인의 설계를 마친 후 두 사람은 잠시 저녁을 먹으러 갔다. 그 사이 해커가 침임해 블록 하나를 지웠다. 지운 블록은 빈 칸이 되어있다.

해커가 어떤 칸을 지웠고, 그 칸에는 원래 어떤 블록이 있었는지 구하는 프로그램을 작성하시오.


입력 :

첫째 줄에 유럽의 크기 R과 C가 주어진다. (1 ≤ R, C ≤ 25)

다음 R개 줄에는 C개 글자가 주어지며, 다음과 같은 글자로 이루어져 있다.

  • 빈칸을 나타내는 ’.
  • 블록을 나타내는 ’|‘(아스키 124), ’-’,’+’,’1’,’2’,’3’,’4
  • 모스크바의 위치를 나타내는 ’M‘과 자그레브를 나타내는 ’Z’. 두 글자는 한 번만 주어진다.

항상 답이 존재하고, 가스의 흐름이 유일한 경우만 입력으로 주어진다, 또, 모스크바와 자그레브가 하나의 블록과 인접해 있는 입력만 주어진다. 또, 불필요한 블록이 존재하지 않는다. 즉, 없어진 블록을 추가하면, 모든 블록에 가스가 흐르게 된다.


출력 :

지워진 블록의 행과 열 위치를 출력하고, 어떤 블록이었는지를 출력한다.




풀이 :

처음에는 문제가 그리 어려워보이진 않았는데 … 문제를 해결하려 하면 할수록, 보이지 않았던 케이스들이 보여서 많은 시간이 소요된 문제였다..

처음에 로직을 꼼꼼하게 짜보려고 시도했지만 놓친 로직을 다시 포함해가면서 짜다보니 코드가 길어진 것 같다. 연습이 더 필요하다.


[ 주의사항 ]

  • 가스의 흐름은 유일하며 지운 블럭은 단 하나이다. 따라서 출발위치를 잡아서(출발위치는 M, Z 위치에 인접하고 있는 블록 하나를 선택한다.) 경로를 따라가다 ’.’ 위치를 찾으면 7개의 블럭을 대입하며 가능 경로가 만들어지는지 판단한다. 여기서 가장 주의해야할 점은 단순히 M -> Z로 이어지는 경로라고 해서 답이 되지는 않는다. M->Z로 이어지는 경로이면서 모든 블럭을 거쳐가는 경로를 찾아야 답이다.
  • 추가로 주의해야할 점은 블록을 모두 거쳐갔는지 판단해줄 때 ’+’ 블록의 경우 두 번 지나칠 수 있기 때문에 사용 여부를 boolean 배열과 count를 같이 사용해 구현해주어야 한다.
  • 블럭을 코드로 구현할 때에 나는 배열로 구현하였다. 배열의 인덱스 번호를 출입 방향으로 가정하고, 해당 인덱스에 저장된 값을 출구 방향으로 가정하여 2차원 배열로 구현해주었다.

[ 세부 구현사항 ]

  1. 모스크바 자그레브 인접 블록 중 하나를 골라서 경로 탐색을 시작한다. -> 여기서 모스크바 혹은 자그레브에 인접한 블록이 지워졌을 수 있기 때문에 모스크바 인접 블록이 없으면 자그래브 인접 블록을 탐색하는 코드까지 구현해야 한다.
  2. 빈칸을 만날 경우 7개 블럭을 하나씩 넣어보면서 연결 경로가 만들어지는지 파악한다 -> 연결경로 파악에는 재귀 함수를 구현해서 경로탐색을 진행하였다.
  3. 연결되는 경우가 되면 해당 대입 블록을 출력해준다.


코드를 좀 더 깔끔하게 짜려는 노력을 더 기울여보자 !




코드 :

코드 보기/접기
#include <iostream>

using namespace std;
int inoutary[7][4] = {
        {-1, -1, 1,  0},
        {-1, 0,  3,  -1},
        {3,  2,  -1, -1},
        {1,  -1, -1, 2},
        {-1, 1,  -1, 3},
        {0,  -1, 2,  -1},
        {0,  1,  2,  3}
};
int rows, cols, di[4] = {0, 1, 0, -1}, dj[4] = {1, 0, -1, 0}, blocknums = 1;
char map[25][25], blocks[7] = {'1', '2', '3', '4', '|', '-', '+'}, finch;
bool usedblocks[25][25];

int findOutDirection(int indir, int blocknum) {
    return (blocknum == -1) ? -1 : inoutary[blocknum][indir];
}

int returnBlockNumber(char ch) {
    switch (ch) {
        case '1':
            return 0;
        case '2':
            return 1;
        case '3':
            return 2;
        case '4':
            return 3;
        case '|':
            return 4;
        case '-':
            return 5;
        case '+':
            return 6;
        default:
            return -1;
    }
}

bool insertNewBlock(int curx, int cury, int curdir, int curcnt) {
    if (curx < 0 || curx >= rows || cury < 0 || cury >= cols) return false;
    if (map[curx][cury] == finch) return (blocknums == curcnt);
    if (map[curx][cury] == '.' || curdir == -1) return false;

    bool retval;

    if (!usedblocks[curx][cury]) {
        usedblocks[curx][cury] = true;
        retval = insertNewBlock(curx + di[curdir], cury + dj[curdir],
                                findOutDirection(curdir, returnBlockNumber(map[curx + di[curdir]][cury + dj[curdir]])),
                                curcnt + 1);
        usedblocks[curx][cury] = false;
    } else
        retval = insertNewBlock(curx + di[curdir], cury + dj[curdir],
                                findOutDirection(curdir, returnBlockNumber(map[curx + di[curdir]][cury + dj[curdir]])),
                                curcnt);

    return retval;
}

int main() {
    char input;
    int mx, my, zx, zy, curx, cury, curdir = -1, blockcnt = 1;
    cin >> rows >> cols;

    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++) {
            cin >> input;
            map[i][j] = input;
            if (input == 'M') {
                mx = i;
                my = j;
            } else if (input == 'Z') {
                zx = i;
                zy = j;
            } else if (input != '.') blocknums++;
        }

    for (int k = 0; k < 4; k++) {
        int cmpx = mx + di[k], cmpy = my + dj[k];
        if (cmpx < 0 || cmpx >= rows || cmpy < 0 || cmpy >= cols) continue;
        int cmpdir = findOutDirection(k, returnBlockNumber(map[cmpx][cmpy]));
        if (map[cmpx][cmpy] != '.' && map[cmpx][cmpy] != 'Z' && cmpdir != -1) {
            curx = cmpx;
            cury = cmpy;
            curdir = cmpdir;
            finch = 'Z';
            usedblocks[curx][cury] = true;
            break;
        }
    } // 모스크바 출발 블럭 찾기

    if (curdir == -1) {
        for (int k = 0; k < 4; k++) {
            int cmpx = zx + di[k], cmpy = zy + dj[k];
            if (cmpx < 0 || cmpx >= rows || cmpy < 0 || cmpy >= cols) continue;
            int cmpdir = findOutDirection(k, returnBlockNumber(map[cmpx][cmpy]));
            if (map[cmpx][cmpy] != '.' && map[cmpx][cmpy] != 'M' && cmpdir != -1) {
                curx = cmpx;
                cury = cmpy;
                curdir = cmpdir;
                finch = 'M';
                usedblocks[curx][cury] = true;
                break;
            }
        }
    }   // 자그레브 출발 블럭 찾기

    while (true) {
        curx += di[curdir];
        cury += dj[curdir];
        if (map[curx][cury] == '.') break;
        if (!usedblocks[curx][cury]) blockcnt++;
        usedblocks[curx][cury] = true;
        curdir = findOutDirection(curdir, returnBlockNumber(map[curx][cury]));
    }   // 빈 칸 찾을 때 까지 경로 탐색

    for (int k = 0; k < 7; k++) {
        map[curx][cury] = blocks[k];
        if (insertNewBlock(curx, cury, findOutDirection(curdir, returnBlockNumber(map[curx][cury])), blockcnt)) {
            cout << curx + 1 << ' ' << cury + 1 << ' ' << map[curx][cury] << '\n';
            break;
        }
    }

    return 0;
}


AlgorithmalgorithmbaekjoonC++