본문 바로가기
Algorithm/Baekjoon

[백준] 아기상어(16236)

by foreverever 2019. 4. 2.
반응형

아기 상어(16236번)

문제

N×N 크기의 공간에 물고기 M마리와 아기 상어 1마리가 있다. 공간은 1×1 크기의 정사각형 칸으로 나누어져 있다. 한 칸에는 물고기가 최대 1마리 존재한다.

 

아기 상어와 물고기는 모두 크기를 가지고 있고, 이 크기는 자연수이다. 가장 처음에 아기 상어의 크기는 2이고, 아기 상어는 1초에 상하좌우로 인접한 한 칸씩 이동한다.

 

아기 상어는 자신의 크기보다 큰 물고기가 있는 칸은 지나갈 수 없고, 나머지 칸은 모두 지나갈 수 있다. 아기 상어는 자신의 크기보다 작은 물고기만 먹을 수 있다. 따라서, 크기가 같은 물고기는 먹을 수 없지만, 그 물고기가 있는 칸은 지나갈 수 있다.

 

아기 상어가 어디로 이동할지 결정하는 방법은 아래와 같다.

  • 더 이상 먹을 수 있는 물고기가 공간에 없다면 아기 상어는 엄마 상어에게 도움을 요청한다.
  • 먹을 수 있는 물고기가 1마리라면, 그 물고기를 먹으러 간다.
  • 먹을 수 있는 물고기가 1마리보다 많다면, 거리가 가장 가까운 물고기를 먹으러 간다.
  • 거리는 아기 상어가 있는 칸에서 물고기가 있는 칸으로 이동할 때, 지나야하는 칸의 개수의 최소값이다.
  • 거리가 가까운 물고기가 많다면, 가장 위에 있는 물고기, 그러한 물고기가 여러마리라면, 가장 왼쪽에 있는 물고기를 먹는다.

아기 상어의 이동은 1초 걸리고, 물고기를 먹는데 걸리는 시간은 없다고 가정한다. 즉, 아기 상어가 먹을 수 있는 물고기가 있는 칸으로 이동했다면, 이동과 동시에 물고기를 먹는다. 물고기를 먹으면, 그 칸은 빈 칸이 된다.

 

아기 상어는 자신의 크기와 같은 수의 물고기를 먹을 때 마다 크기가 1 증가한다. 예를 들어, 크기가 2인 아기 상어는 물고기를 2마리 먹으면 크기가 3이 된다.

 

공간의 상태가 주어졌을 때, 아기 상어가 몇 초 동안 엄마 상어에게 도움을 요청하지 않고 물고기를 잡아먹을 수 있는지 구하는 프로그램을 작성하시오.

입력

첫째 줄에 공간의 크기 N(2 ≤ N ≤ 20)이 주어진다.

둘째 줄부터 N개의 줄에 공간의 상태가 주어진다. 공간의 상태는 0, 1, 2, 3, 4, 5, 6, 9로 이루어져 있고, 아래와 같은 의미를 가진다.

  • 0: 빈 칸
  • 1, 2, 3, 4, 5, 6: 칸에 있는 물고기의 크기
  • 9: 아기 상어의 위치

아기 상어는 공간에 한 마리 있다.

풀이

bfs와 우선순위 큐를 활용한 문제이다. bfs를 한번 돌면서 배열의 상태값이 주어진 주건에 따라 계속 변하기 때문에 이를 유의하도록 하자.

 

또한, 아기상어 및 물고기가 가지는 상태값이 여러개 존재하므로, 관리하기 쉽게 구조체 혹은 클래스를 만들도록 한다. (구조체로 진행)

 

시간복잡도는 현재 상어위치에서 bfs탐색 할 경우 n^2, 해당 bfs를 최대 n^2번(조건에 맞는 물고기 수가 최대인 경우) 수행할 수 있으므로, O(n^4)이 된다. n은 최대 20이므로, 최악의 경우 20^4 = 16만 정도 되므로 시간은 충분하다.

 

먼저, 물고기 및 상어의 상황을 나타내는 2차원 배열을 입력 받도록 한다. 이때, 배열값이 9인 경우 해당 배열의 위치(y,x)를 상어 위치값에 저장하도록 한다.

 

상어의 위치를 알았으므로, 해당 위치부터 bfs탐색을 시작한다. 이때, 상어의 사이즈보다 작은 물고기들을 찾고 최소거리를 해당 물고기의 거리 상태값에 저장하도록 한다. 또한, 우선순위 큐에 해당 물고기 객체를 저장한다.

 

이때, 정렬기준이 정말 중요하다. 이미 우선순위 큐에 들어가는 물고기들은 아기상어가 먹을 수 있는 물고기들이기 때문에 크기 조건은 만족한다. 따라서 정렬기준은 다음과 같다.

  1. 거리순

  2. 거리가 같다면, y좌표가 작은 순

  3. y좌표가 같다면, x좌표가 작은 순

    구조체 정렬은 구조체 타입의 cmp를 따로 만들어, 연산자 오버로딩을 해야한다.

    struct cmp {
    bool operator()(Fish &f1, Fish &f2) {
        if (f1.dist == f2.dist) {
            if (f1.y == f2.y) {
                return f1.x > f2.x;
            }
            return f1.y > f2.y;
        }
        return f1.dist > f2.dist;
    }
    };

해당 정렬 기준을 우선순위 큐에 적용하고싶다면, 우선순위 큐 정의를 다음과 같이 하도록 한다.

priority_queue<Fish, vector<Fish>, cmp> pq;

결국, 우선순위 큐의 맨 앞에있는 물고기가 현재 아기상어가 먹을 수 있는 물고기가 되는 것이다. 다음 bfs를 위해 해당 물고기의 좌표값을 아기상어 좌표값에 대입하도록 한다. 또한, 물고기가 가지고 있는 최소 거리값을 아기상어의 총 이동거리값에 더해주도록 한다.

 

위를 반복해주며 bfs탐색을 진행한다. 우선순위 큐에 더 이상 물고기가 안들어 갈 경우, 먹을 수 있는 물고기가 없다는 뜻이므로 종료조건이 된다.

 

bfs 한 회가 끝나면, visit, dist 배열을 초기화 하는것을 잊지 말자. (memset으로 초기화 하면 빠르다.)

C++ 소스코드

#include <iostream>
#include <queue>
#include <string.h>
using namespace std;

struct Pos {
    int y, x;
};

struct Fish {
    int eat = 0, dist = 0;    //eat : 현재 크기에서 아기상어가 먹은 물고기 개수
    int y, x, size;
    Fish(int y, int x, int dist, int size) {
        this->y = y;
        this->x = x;
        this->dist = dist;    //아기상어로부터 최소 거리
        this->size = size;    //아기상어 혹은 물고기의 크기
    }
    Fish() {}
};
int arr[21][21], dist[21][21];
bool visit[21][21];
int dy[4] = { -1,1,0,0 };
int dx[4] = { 0,0,-1,1 };
int N;
queue<Pos> q;
Fish shark;

struct cmp {
    bool operator()(Fish &f1, Fish &f2) {
        if (f1.dist == f2.dist) {
            if (f1.y == f2.y) {
                return f1.x > f2.x;
            }
            return f1.y > f2.y;
        }
        return f1.dist > f2.dist;
    }
};

bool bfs() {
    q.push({ shark.y,shark.x });
    visit[shark.y][shark.x] = true;
    priority_queue<Fish, vector<Fish>, cmp> pq;

    while (!q.empty()) {
        Pos cur = q.front();
        q.pop();
        for (int k = 0; k < 4; k++) {
            int my = cur.y + dy[k];
            int mx = cur.x + dx[k];
            if (my < 0 || mx < 0 || my >= N || mx >= N) continue;
            if (visit[my][mx] == false && arr[my][mx] <= shark.size) {
                visit[my][mx] = true;
                dist[my][mx] = dist[cur.y][cur.x] + 1;
                q.push({ my,mx });
                if (arr[my][mx] > 0 && arr[my][mx] < shark.size) {
                    pq.push(Fish(my, mx, dist[my][mx], arr[my][mx]));
                }
            }
        }
    }


    if (!pq.empty()) {    //우선순위 큐 맨 앞의 물고기가 현재 아기상어가 가장 먼저 먹을 수 있는 물고기
        Fish food = pq.top();
        arr[shark.y][shark.x] = 0;
        shark.y = food.y;
        shark.x = food.x;
        shark.dist += food.dist;
        if (++shark.eat == shark.size) {
            shark.size++;
            shark.eat = 0;    //상어 크기 1 증가 시, 지금까지 먹은 개수 초기화
        }
        memset(dist, 0, sizeof(dist));
        memset(visit, false, sizeof(visit));
        return true;
    }
    return false;
}

int main() {
    scanf("%d", &N);
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            scanf("%d", &arr[i][j]);
            if (arr[i][j] == 9) {
                shark.size = 2;
                shark.y = i;
                shark.x = j;
            }
        }
    }
    while (bfs());
    printf("%d", shark.dist);
    return 0;
}

부족한 부분

2018 하반기 삼성전자 문제였다.

 

2차원 배열에 존재하는 모든 물고기의 최소거리(시간)를 구하는 문구에서 bfs를 떠올렸고, 상어와 물고기의 상태값을 편하게 관리하기 위해 구조체를 떠올렸다. 또한, 아기상어가 먹을 수 있는 물고기 중 우선순위 조건이 여러개 있으므로, 우선순위 큐를 이용하였다. 이때 반드시 정렬기준을 조건에 맞게 만들도록 했다.

 

총 1시간 반 정도 걸린 것 같다. 1시간만에 구현을 끝냈지만, 중간에 상어의 크기가 1증가하면, 상어가 먹은 물고기의 개수는 0으로 초기화 해야하는데, 해당 작업을 안해서 30분정도 디버깅하는데 애먹었다.

 

항상 복잡한 알고리즘을 구현하다 보면, 한 두개 조건을 빼먹어서 디버깅하는데 시간을 낭비하는 경우가 많다.

이를 방지하기 위해 노트에 조건을 적으려고 노력한다. 그러나 코딩을 하다보면 수정해야 하는 부분이 발생하고 (노트에 적은 조건이 필요없어지거나, 간과한 조건이 있을 경우) 코드를 수정하는 과정에서 놓치는 부분이 발생한다.

 

왜 테스트 코드를 작성하면서 코딩하는지 매번 깨닫게 된다.
(근데 막상 알고리즘 시험을 보면, 시간에 쫓겨 잘 안하게 되는.. )

반응형

'Algorithm > Baekjoon' 카테고리의 다른 글

[백준] 스타트와 링크(14889)  (0) 2019.04.05
[백준] 테트로미노(14500)  (0) 2019.04.04
[백준] 치킨배달(15686)  (0) 2019.04.03
[백준] 수 고르기(2230)  (1) 2019.04.02
[백준] 도영이가 만든 맛있는 음식(2961)  (0) 2019.04.02