궤도

[백준] 19236번 : 청소년 상어 본문

💻 현생/⛓ 알고리즘

[백준] 19236번 : 청소년 상어

영이오 2021. 7. 12. 19:01

문제

 


풀이

 

할 일을 정리해보자...

 

1. 상어가 위치한 곳에 있는 물고기를 먹음

   1.1 상어의 위치, 방향 정보 갱신

   1.2 물고기 삭제 처리

   1.3 상어의 새로운 위치를 공간에 기록

2. 1~16번 물고기 이동(아직 살아있는 물고기만)

3. 상어가 이동할 수 있는 모든 공간 탐색(백트래킹 재귀 함수)

   3.1 상어가 더 이상 이동할 수 없으면 최댓값 갱신

 

공간의 현재 상태를 기록할 4x4 2차원 배열과 상어+물고기의 위치, 방향 정보를 기록할 1차원 배열이 필요하다.

상어는 여러 칸을 이동할 수 있지만 방향을 바꿀 수 없고, 물고기는 한 칸만 이동할 수 있지만 방향을 바꿀 수 있다는 차이점을 염두해야 한다.


소스코드

 

#include <iostream>
#include <vector>

using namespace std;

struct info {
    int r, c, d;
};

int max_fish = 0;
pair<int, int> direction[8] = {{-1, 0}, {-1, -1}, {0,  -1}, {1,  -1}, {1,  0}, {1,  1}, {0,  1}, {-1, 1}};

void moveFish(int idx, vector<info> &pos, vector<vector<int>> &board) {
    int row = pos[idx].r, col = pos[idx].c, dir = pos[idx].d; //행, 열, 방향
    do {
        int nr = row + direction[dir].first;
        int nc = col + direction[dir].second;
        if ((nr >= 0) && (nr < 4) && (nc >= 0) && (nc < 4) && (board[nr][nc] != 0)) { //범위내에 있고 상어 없으면
            swap(board[row][col], board[nr][nc]); //위치 교환
            pos[idx] = {nr, nc, dir}; //이번에 이동한 물고기 정보 갱신
            if (board[row][col] != -1) //빈공간으로 이동한게 아니라면(물고기와 위치를 바꾼거라면)
                pos[board[row][col]] = {row, col, pos[board[row][col]].d}; //바뀐 물고기 정보 갱신
            break;
        }
        dir = (dir + 1) % 8; //방향 바꾸기
    } while (dir != pos[idx].d);
}

void backtracking(int r, int c, int fish, vector<info> pos, vector<vector<int>> board) {
    fish += board[r][c]; //물고기 먹음
    int dir = pos[board[r][c]].d; //먹은 물고기의 방향
    pos[0] = {r, c, dir}; //상어 정보 갱신
    pos[board[r][c]].d = -1; //먹힌 물고기 갱신
    board[r][c] = 0; //상어 위치 표시
    for (int i = 1; i <= 16; i++) {
        if (pos[i].d != -1) //먹힌 물고기가 아니라면 이동
            moveFish(i, pos, board);
    }
    int nr = r, nc = c;
    while (true) { //범위 벗어날 때까지 해당 방향으로 이동
        nr += direction[dir].first;
        nc += direction[dir].second;
        if ((nr < 0) || (nr >= 4) || (nc < 0) || (nc >= 4)) //범위 벗어남
            break;
        if (board[nr][nc] != -1) { //물고기가 존재
            board[r][c] = -1; //원래 상어가 있던 곳은 빈공간으로
            backtracking(nr, nc, fish, pos, board);
        }
    }
    max_fish = max(max_fish, fish); //최댓값 갱신
}

int main() {
    vector<vector<int>> board(4, vector<int>(4));
    vector<info> pos(17);
    int a, b;

    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            cin >> a >> b;
            board[i][j] = a;
            pos[a] = {i, j, b - 1};
        }
    }
    backtracking(0, 0, 0, pos, board);
    cout << max_fish;
}

전체 소스코드다.

 

struct info {
    int r, c, d;
};

int max_fish = 0;
pair<int, int> direction[8] = {{-1, 0}, {-1, -1}, {0,  -1}, {1,  -1}, {1,  0}, {1,  1}, {0,  1}, {-1, 1}};

물고기, 상어의 정보를 저장할 info 구조체와 먹을 물고기 번호의 최댓값을 저장할 max_fish 변수,

그리고 8개의 방향이다.

 

    vector<vector<int>> board(4, vector<int>(4));
    vector<info> pos(17);
    int a, b;

    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            cin >> a >> b;
            board[i][j] = a;
            pos[a] = {i, j, b - 1};
        }
    }

물고기가 16마리 상어가 1마리니까 pos 벡터는 17만큼 할당하고 board 2차원 벡터는 4x4로 만든다.

그리고 입력을 받는데, 방향이 0~7이 되도록 b-1을 해준다.

 

    backtracking(0, 0, 0, pos, board);

함수 호출...시작점은 (0, 0)이고 먹은 물고기의 수는 0이다.

 

void backtracking(int r, int c, int fish, vector<info> pos, vector<vector<int>> board) {
    fish += board[r][c]; //물고기 먹음
    int dir = pos[board[r][c]].d; //먹은 물고기의 방향
    pos[0] = {r, c, dir}; //상어 정보 갱신
    pos[board[r][c]].d = -1; //먹힌 물고기 갱신
    board[r][c] = 0; //상어 위치 표시
    for (int i = 1; i <= 16; i++) {
        if (pos[i].d != -1) //먹힌 물고기가 아니라면 이동
            moveFish(i, pos, board);
    }
    int nr = r, nc = c;
    while (true) { //범위 벗어날 때까지 해당 방향으로 이동
        nr += direction[dir].first;
        nc += direction[dir].second;
        if ((nr < 0) || (nr >= 4) || (nc < 0) || (nc >= 4)) //범위 벗어남
            break;
        if (board[nr][nc] != -1) { //물고기가 존재
            board[r][c] = -1; //원래 상어가 있던 곳은 빈공간으로
            backtracking(nr, nc, fish, pos, board);
        }
    }
    max_fish = max(max_fish, fish); //최댓값 갱신
}

원래는 백트래킹에서 unvisited 처리를 할 때 pos와 board를 복사해놓고 다시 쓰는 방법으로 구현할까 했는데, 귀찮아서 그냥 파라미터로 넘기기로 했다.

 

    fish += board[r][c]; //물고기 먹음
    int dir = pos[board[r][c]].d; //먹은 물고기의 방향
    pos[0] = {r, c, dir}; //상어 정보 갱신
    pos[board[r][c]].d = -1; //먹힌 물고기 갱신
    board[r][c] = 0; //상어 위치 표시

먼저 물고기를 먹었으니 fish 변수를 갱신하고, 해당 물고기의 방향을 저장해둔다.

그리고 그에 맞춰 상어의 행, 열, 방향 정보를 갱신하고, 먹힌 물고기의 방향을 -1로 바꿔준다. 먹혔다는 뜻이다.

마지막으로 원래 물고기가 있던 자리를 0으로 갱신해서 상어가 있다고 표시한다.

 

    for (int i = 1; i <= 16; i++) {
        if (pos[i].d != -1) //먹힌 물고기가 아니라면 이동
            moveFish(i, pos, board);
    }

모든 물고기에 대해 방향 정보가 -1이 아니라면, 그니까 먹힌 물고기가 아니라면 moveFish를 통해 움직여준다.

 

void moveFish(int idx, vector<info> &pos, vector<vector<int>> &board) {
    int row = pos[idx].r, col = pos[idx].c, dir = pos[idx].d; //행, 열, 방향
    do {
        int nr = row + direction[dir].first;
        int nc = col + direction[dir].second;
        if ((nr >= 0) && (nr < 4) && (nc >= 0) && (nc < 4) && (board[nr][nc] != 0)) { //범위내에 있고 상어 없으면
            swap(board[row][col], board[nr][nc]); //위치 교환
            pos[idx] = {nr, nc, dir}; //이번에 이동한 물고기 정보 갱신
            if (board[row][col] != -1) //빈공간으로 이동한게 아니라면(물고기와 위치를 바꾼거라면)
                pos[board[row][col]] = {row, col, pos[board[row][col]].d}; //바뀐 물고기 정보 갱신
            break;
        }
        dir = (dir + 1) % 8; //방향 바꾸기
    } while (dir != pos[idx].d);
}

방향을 옮길 수 있는 횟수는 최대 8번이다.

 

        int nr = row + direction[dir].first;
        int nc = col + direction[dir].second;
        if ((nr >= 0) && (nr < 4) && (nc >= 0) && (nc < 4) && (board[nr][nc] != 0)) { //범위내에 있고 상어 없으면
            swap(board[row][col], board[nr][nc]); //위치 교환
            pos[idx] = {nr, nc, dir}; //이번에 이동한 물고기 정보 갱신
            if (board[row][col] != -1) //빈공간으로 이동한게 아니라면(물고기와 위치를 바꾼거라면)
                pos[board[row][col]] = {row, col, pos[board[row][col]].d}; //바뀐 물고기 정보 갱신
            break;
        }
        dir = (dir + 1) % 8; //방향 바꾸기

dir 방향으로 1칸 움직인 nr, nc에 대해, 이게 범위 내에 있고 그 자리에 상어가 있는게 아니라면

swap으로 위치를 교환한다. 이동하게 될 공간이 빈 공간이라도 상관없다. 어차피 해당 물고기가 빈 공간으로 이동하게 되면 물고기가 있던 곳이 새로운 빈 공간이 될 것이기 때문에 -1과 swap하는 셈이다.

 

swap을 하면 board의 상태만 갱신되는 것이니, pos[idx]도 갱신하고 만약 물고기와 자리를 바꾼거라면 바뀐 물고기의 위치 정보도 갱신한다.

 

만약 범위를 벗어나거나 상어가 있어서 이동하지 못했다면 dir값을 갱신하면서 다음 방향을 살펴본다.

 

    int nr = r, nc = c;
    while (true) { //범위 벗어날 때까지 해당 방향으로 이동
        nr += direction[dir].first;
        nc += direction[dir].second;
        if ((nr < 0) || (nr >= 4) || (nc < 0) || (nc >= 4)) //범위 벗어남
            break;
        if (board[nr][nc] != -1) { //물고기가 존재
            board[r][c] = -1; //원래 상어가 있던 곳은 빈공간으로
            backtracking(nr, nc, fish, pos, board);
        }
    }

물고기가 전부 이동했으니 상어를 움직일 차례다.

상어의 방향으로 한칸씩 움직여보면서 물고기가 존재하는 칸이라면 원래 상어가 있던 곳을 빈공간으로 바꿔준 뒤, 새로운 좌표에 대해 재귀함수를 호출한다.

 

    max_fish = max(max_fish, fish); //최댓값 갱신

가능한 모든 칸을 탐색한 뒤엔 최댓값을 갱신한다.

Comments