궤도

[백준] 16236번 : 아기 상어 본문

💻 현생/⛓ 알고리즘

[백준] 16236번 : 아기 상어

영이오 2021. 5. 10. 19:53

문제

 


풀이

 

계속해서 상어를 이동시키며 이동할 때마다 bfs를 실행해야 한다.

다행히 입력 범위가 그렇게 크지 않아 bfs를 여러번 해도 시간 초과는 발생하지 않는다.

 

상어가 물고기를 먹는 우선순위 때문에 좀 헤맸다.

처음엔 단순히 '상-좌-우-하'의 순서로 탐색하면 되겠거니 했는데 '예제 입력 4'에서 틀린 답이 나왔다.

 

그래서 최단 경로에 위치한 모든 물고기들을 찾은 뒤, 그 물고기들 중에서 가장 우선순위가 높은 물고기를 찾는 방법으로 코드를 다시 작성했다.


소스코드

 

#include <iostream>
#include <vector>
#include <queue>
#include <utility>
#include <algorithm>

using namespace std;

vector<vector<int>> board;
int shark_size = 2; //상어의 크기
int fish_cnt, result; //먹은 물고기의 수, 이동하는데 걸린 총 시간
pair<int, int> dir[4] = {{-1, 0},  //상
                         {1,  0},  //하
                         {0,  -1}, //좌
                         {0,  1}}; //우

bool cmp(const pair<int, int> &p1, const pair<int, int> &p2) { //정렬
    if (p1.first == p2.first)
        return p1.second < p2.second;
    return p1.first < p2.first;
}

pair<int, int> bfs(pair<int, int> shark) {
    int size = board.size(), min_dist = 1000;
    queue<pair<int, int>> q; //상어가 갈 수 있는 곳을 담은 큐
    vector<vector<int>> dist; //상어의 방문 여부 및 이동 거리
    dist.assign(size, vector<int>(size, -1));
    vector<pair<int, int>> candi; //상어가 최종적으로 이동할 곳의 후보

    dist[shark.first][shark.second] = 0;
    q.push(shark);
    while (!q.empty()) {
        int row = q.front().first;
        int col = q.front().second;
        q.pop();

        if (dist[row][col] < min_dist) { //dist[row][col]이 min_dist 이상이라면, 물고기가 있어도 최단거리가 아님
            for (int i = 0; i < 4; i++) {
                int nr = row + dir[i].first;
                int nc = col + dir[i].second;
                if ((nr >= 0) && (nr < size) && (nc >= 0) && (nc < size) && (dist[nr][nc] == -1) && //범위 내에 있는 미방문 좌표
                    (board[nr][nc] <= shark_size)) { //상어가 갈 수 있다면
                    dist[nr][nc] = dist[row][col] + 1; //거리 갱신
                    if ((board[nr][nc] > 0) && (board[nr][nc] < shark_size)) { //상어가 먹을 수 있는 물고기라면
                        candi.push_back(make_pair(nr, nc)); //최종 이동 후보에 넣음
                        min_dist = dist[nr][nc]; //최단 거리 갱신
                    }
                    if (dist[nr][nc] < min_dist) //먹을 수 있는 물고기를 찾았다면 더 나갈 필요 없음
                        q.push(make_pair(nr, nc));
                }
            }
        }
    }
    if (candi.empty()) //후보 배열이 비어있다면 상어가 이동할 수 없는 것
        return make_pair(-1, -1);

    sort(candi.begin(), candi.end(), cmp); //정렬
    fish_cnt++; //먹은 물고기의 수 증가
    if (fish_cnt == shark_size) { //먹은 물고기의 수가 상어의 크기와 같다면
        fish_cnt = 0;
        shark_size++;
    }
    board[candi[0].first][candi[0].second] = 9; //상어 이동
    board[shark.first][shark.second] = 0; //상어의 원래 위치 0으로 변경
    result += min_dist; //활동 시간 증가
    return make_pair(candi[0].first, candi[0].second); //상어의 새로운 위치 반환
}

int main() {
    int N;
    pair<int, int> shark_pos;

    cin >> N;
    board.assign(N, vector<int>(N));
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            cin >> board[i][j];
            if (board[i][j] == 9) //상어의 초기 위치
                shark_pos = make_pair(i, j);
        }
    }
    while ((shark_pos.first != -1) && (shark_pos.second != -1)) //상어가 더 이동할 수 없을 때까지 이동
        shark_pos = bfs(shark_pos);
    cout << result;
}

전체 소스코드다.

 

vector<vector<int>> board;
int shark_size = 2; //상어의 크기
int fish_cnt, result; //먹은 물고기의 수, 이동하는데 걸린 총 시간
pair<int, int> dir[4] = {{-1, 0},  //상
                         {1,  0},  //하
                         {0,  -1}, //좌
                         {0,  1}}; //우

상어의 크기, 현재 먹은 물고기의 수, 활동 시간은 전역변수로 관리한다.

 

    cin >> N;
    board.assign(N, vector<int>(N));
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            cin >> board[i][j];
            if (board[i][j] == 9) //상어의 초기 위치
                shark_pos = make_pair(i, j);
        }
    }

board에 입력을 받으면서 상어의 초기 위치도 저장한다.

 

    while ((shark_pos.first != -1) && (shark_pos.second != -1)) //상어가 더 이동할 수 없을 때까지 이동
        shark_pos = bfs(shark_pos);

그리고 상어의 위치를 계속 갱신할건데, bfs는 상어가 더이상 이동할 수 없을 때 (-1, -1)을 반환하도록 했다.

그러니 while문의 종료 조건도 (-1, -1)로 하면 된다.

 

pair<int, int> bfs(pair<int, int> shark) {
    int size = board.size(), min_dist = 1000;
    queue<pair<int, int>> q; //상어가 갈 수 있는 곳을 담은 큐
    vector<vector<int>> dist; //상어의 방문 여부 및 이동 거리
    dist.assign(size, vector<int>(size, -1));
    vector<pair<int, int>> candi; //상어가 최종적으로 이동할 곳의 후보

    dist[shark.first][shark.second] = 0;
    q.push(shark);
    while (!q.empty()) {
        int row = q.front().first;
        int col = q.front().second;
        q.pop();

        if (dist[row][col] < min_dist) { //dist[row][col]이 min_dist 이상이라면, 물고기가 있어도 최단거리가 아님
            for (int i = 0; i < 4; i++) {
                int nr = row + dir[i].first;
                int nc = col + dir[i].second;
                if ((nr >= 0) && (nr < size) && (nc >= 0) && (nc < size) && (dist[nr][nc] == -1) && //범위 내에 있는 미방문 좌표
                    (board[nr][nc] <= shark_size)) { //상어가 갈 수 있다면
                    dist[nr][nc] = dist[row][col] + 1; //거리 갱신
                    if ((board[nr][nc] > 0) && (board[nr][nc] < shark_size)) { //상어가 먹을 수 있는 물고기라면
                        candi.push_back(make_pair(nr, nc)); //최종 이동 후보에 넣음
                        min_dist = dist[nr][nc]; //최단 거리 갱신
                    }
                    if (dist[nr][nc] < min_dist) //먹을 수 있는 물고기를 찾았다면 더 나갈 필요 없음
                        q.push(make_pair(nr, nc));
                }
            }
        }
    }
    if (candi.empty()) //후보 배열이 비어있다면 상어가 이동할 수 없는 것
        return make_pair(-1, -1);

    sort(candi.begin(), candi.end(), cmp); //정렬
    fish_cnt++; //먹은 물고기의 수 증가
    if (fish_cnt == shark_size) { //먹은 물고기의 수가 상어의 크기와 같다면
        fish_cnt = 0;
        shark_size++;
    }
    board[candi[0].first][candi[0].second] = 9; //상어 이동
    board[shark.first][shark.second] = 0; //상어의 원래 위치 0으로 변경
    result += min_dist; //활동 시간 증가
    return make_pair(candi[0].first, candi[0].second); //상어의 새로운 위치 반환
}

bfs 함수는 먹을 수 있는 물고기의 좌표를 저장하는 부분과 물고기를 실제로 먹는 부분으로 나뉜다.

 

    int size = board.size(), min_dist = 1000;
    queue<pair<int, int>> q; //상어가 갈 수 있는 곳을 담은 큐
    vector<vector<int>> dist; //상어의 방문 여부 및 이동 거리
    dist.assign(size, vector<int>(size, -1));
    vector<pair<int, int>> candi; //상어가 최종적으로 이동할 곳의 후보

    dist[shark.first][shark.second] = 0;
    q.push(shark);

먼저 bfs에 기본적으로 필요한 큐와 거리를 저장하는 벡터를 선언했다.

그리고 최단거리에 있는 물고기들의 좌표를 저장할 candi 벡터와 그 최단거리를 저장할 min_dist 변수도 선언했다.

 

상어의 처음 위치의 dist를 0으로 초기화 하고 그 위치를 큐에 넣으며 시작한다.

 

    while (!q.empty()) {
        int row = q.front().first;
        int col = q.front().second;
        q.pop();

        if (dist[row][col] < min_dist) { //dist[row][col]이 min_dist 이상이라면, 물고기가 있어도 최단거리가 아님
            for (int i = 0; i < 4; i++) {
                int nr = row + dir[i].first;
                int nc = col + dir[i].second;
                if ((nr >= 0) && (nr < size) && (nc >= 0) && (nc < size) && (dist[nr][nc] == -1) && //범위 내에 있는 미방문 좌표
                    (board[nr][nc] <= shark_size)) { //상어가 갈 수 있다면
                    dist[nr][nc] = dist[row][col] + 1; //거리 갱신
                    if ((board[nr][nc] > 0) && (board[nr][nc] < shark_size)) { //상어가 먹을 수 있는 물고기라면
                        candi.push_back(make_pair(nr, nc)); //최종 이동 후보에 넣음
                        min_dist = dist[nr][nc]; //최단 거리 갱신
                    }
                    if (dist[nr][nc] < min_dist) //먹을 수 있는 물고기를 찾았다면 더 나갈 필요 없음
                        q.push(make_pair(nr, nc));
                }
            }
        }
    }

특정 좌표의 상하좌우는 그 좌표의 dist값이 min_dist보다 작을 때만 탐색한다.

왜냐하면 특정 좌표에서 상하좌우로 이동하면 무조건 1이 더해질텐데 dist값이 이미 min_dist보다 같거나 컸다면 새롭게 이동한 좌표는 절대 최단 거리에 존재하는 물고기가 될 수 없기 때문이다.

 

                if ((nr >= 0) && (nr < size) && (nc >= 0) && (nc < size) && (dist[nr][nc] == -1) && //범위 내에 있는 미방문 좌표
                    (board[nr][nc] <= shark_size)) { //상어가 갈 수 있다면
                    dist[nr][nc] = dist[row][col] + 1; //거리 갱신
                    if ((board[nr][nc] > 0) && (board[nr][nc] < shark_size)) { //상어가 먹을 수 있는 물고기라면
                        candi.push_back(make_pair(nr, nc)); //최종 이동 후보에 넣음
                        min_dist = dist[nr][nc]; //최단 거리 갱신
                    }
                    if (dist[nr][nc] < min_dist) //먹을 수 있는 물고기를 찾았다면 더 나갈 필요 없음
                        q.push(make_pair(nr, nc));
                }

갈 수 있는 좌표를 계속 탐색하며 큐에 넣어준다.

그러다가 만약 해당 좌표에 먹을 수 있는 물고기가 있다면 min_dist를 갱신하고 그 좌표를 candi 벡터에 넣어준다.

bfs 탐색이기 때문에 처음으로 찾은 물고기의 dist가 min_dist가 될 수 있다.

 

    if (candi.empty()) //후보 배열이 비어있다면 상어가 이동할 수 없는 것
        return make_pair(-1, -1);

    sort(candi.begin(), candi.end(), cmp); //정렬
    fish_cnt++; //먹은 물고기의 수 증가
    if (fish_cnt == shark_size) { //먹은 물고기의 수가 상어의 크기와 같다면
        fish_cnt = 0;
        shark_size++;
    }
    board[candi[0].first][candi[0].second] = 9; //상어 이동
    board[shark.first][shark.second] = 0; //상어의 원래 위치 0으로 변경
    result += min_dist; //활동 시간 증가
    return make_pair(candi[0].first, candi[0].second); //상어의 새로운 위치 반환

while문이 종료되면 candi 벡터에 값이 있는지 확인한다.

만약 값이 없다면 먹을 수 있는 물고기가 없다는 뜻이고 상어가 더이상 이동할 수 없다는 뜻이니 (-1, -1)을 리턴한다.

 

그렇지 않다면 candi 벡터를 정렬한다.

bool cmp(const pair<int, int> &p1, const pair<int, int> &p2) { //정렬
    if (p1.first == p2.first)
        return p1.second < p2.second;
    return p1.first < p2.first;
}

이렇게하면 조건에 맞는 물고기의 좌표가 맨 앞으로 온다.

 

    fish_cnt++; //먹은 물고기의 수 증가
    if (fish_cnt == shark_size) { //먹은 물고기의 수가 상어의 크기와 같다면
        fish_cnt = 0;
        shark_size++;
    }
    board[candi[0].first][candi[0].second] = 9; //상어 이동
    board[shark.first][shark.second] = 0; //상어의 원래 위치 0으로 변경
    result += min_dist; //활동 시간 증가
    return make_pair(candi[0].first, candi[0].second); //상어의 새로운 위치 반환

물고기를 먹어야 하니 fish_cnt를 하나 늘려준다.

근데 만약 이 fish_cnt가 shark_size와 같다면 상어의 크기가 커질 수 있으니 그럴 경우 shark_size를 늘려주고 fist_cnt는 다시 0으로 초기화 한다.

 

그리고 상어의 새로운 위치를 board 벡터에 반영하고 min_dist를 result에 더해준다.

마지막으로 상어의 새로운 위치를 리턴하면 된다.

Comments