아기상어_16236

박상민·2024년 6월 20일
0

백준

목록 보기
30/36
post-thumbnail

문제

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: 아기 상어의 위치
    아기 상어는 공간에 한 마리 있다.

출력

첫째 줄에 아기 상어가 엄마 상어에게 도움을 요청하지 않고 물고기를 잡아먹을 수 있는 시간을 출력한다.

문제 이해

문제가 굉장히 길고 난해하므로 이해하기 쉽도록 조건에 따라 분류해보았다.

간단히 말하자면, 아기상어 문제는 아기상어가 N*N 행렬을 돌아다니며 아래의 조건에 따라 물고기를 다 잡아먹을 때까지 걸리는 시간을 구하는 문제이다.

기본 조건

  • 아기상어는 9로 주어짐
  • 아기상어의 크기: 2
  • 물고기 크기는 행렬에 주어짐

아기상어 이동조건

  • 아기상어는 상하좌우로 움직일 수 있고 한 칸 이동시 1초가 소요됨
  • 아기상어는 자신보다 크기가 작거나 같은 물고기가 있는 칸만 이동할 수 있고 자신보다 큰 물고기가 있는 칸은 이동할 수 없다.
  • 이동거리는 항상 최단거리로 움직인다.
  • 거리가 같다면 가장 위쪽에 있는 물고기를, 그런 물고기가 여러 마리면 가장 왼쪽에 있는 물고기를 먹습니다.

아기상어 먹는조건

  • 아기상어는 자신보다 크기가 작은 물고기만 먹을 수 있다.
  • 아기상어는 자신의 크기와 같은 수의 물고기를 먹을 때마다 크기가 1씩 증가 ex) 크기가 2인 아기상어는 물고기를 2마리 먹은 후 3으로 증가됨
  • 아기상어가 물고기를 먹으면 원래 물고기가 있던 자리는 0으로 초기화됨

예제 입력

4
4 3 2 1
0 0 0 0
0 0 9 0
1 2 3 4

예제 출력

14

코드

초기화

격자 정보와 아기 상어의 초기 위치를 입력받습니다.
아기 상어의 초기 크기와 먹은 물고기 수를 초기화합니다.

const dy = [-1, 0, 0, 1]; // 상 하 방향정의
const dx = [0, -1, 1, 0]; // 좌 우 방향정의
const eats = new Array(7).fill(0); //아기상어가 먹은 물고기의 수

알고리즘 선택

BFS는 모든 간선의 가중치가 동일할 때 최단 경로를 보장하고 아기상어는 모든 간선의 가중치가 동일하므로 BFS를 선택해도 무방하다. 또한 큐를 사용하여 시작 노드에서 가까운 노드부터 탐색합니다. 큐에 들어간 순서대로 노드를 처리하므로, 먼저 도달한 노드가 최단 경로임을 보장한다. 따라서 BFS는 목표 지점에 도달하는 가장 빠른 방법을 찾을 수 있다.

function bfs() {
  let cur = []; //cur는 아기 상어의 현재 위치
  let lv = 2; //lv는 아기 상어의 현재 크기
  let dist = 0; // 아기상어가 이동한 거리

  for (let i = 0; i < N; i++) {
    for (let j = 0; j < N; j++) {
      if (map[i][j] === 9) {
        cur = [i, j];
        map[i][j] = 0; // 빈칸으로 설정(더 이상 아기상어 위치 표시될 필요없음)
      }
    }
  }

탐색

BFS를 이용하여 현재 아기 상어의 위치에서 먹을 수 있는 가장 가까운 물고기를 찾는다.
가장 가까운 물고기를 먹고, 해당 위치로 아기 상어를 이동시킵니다.
물고기를 먹을 때마다 시간을 누적합니다.

while (true) {
  const q = []; //q는 BFS를 위한 큐
  const visited = Array.from({ length: N }, () => new Array(N).fill(false)); //visited는 방문 여부를 기록하는 배열
  const smallq = []; // smallq는 먹을 수 있는 물고기의 위치와 거리를 저장하는 배열
  let movCnt = 0; // movCnt는 현재 이동 횟수 저장. 초기 값은 0

  q.push([...cur, 0]);

  while (q.length) {
    const [y, x, cnt] = q.shift(); //큐의 첫번째 요소를 꺼내 현재 위치와 현재위치까리의 이동거리에 저장
	// 현재 위치에 아기 상어가 먹을 수 있는 물고기가 있는지 확인하는 조건
    if (map[y][x] < lv && map[y][x] !== 0) {
      smallq.push([y, x, cnt]); // 조건을 만족하면, 현재 위치와 이동거리 추가
    }
	
    // 상하좌우 이동
    for (let i = 0; i < 4; i++) {
      const ny = y + dy[i];
      const nx = x + dx[i];
	
      // 새로운 위치가 격자 범위를 벗어나는지 확인
      if (ny < 0 || ny >= N || nx < 0 || nx >= N) continue;
      if (map[ny][nx] > lv) continue; // 새로운 위치에 있는 물고기의 크기가 아기상어의 크기보다 큰 경우 못지나감 -> 넘어감
      if (visited[ny][nx]) continue;  // 이미 방문된 위치면 못지나감 -> 넘어감

      q.push([ny, nx, cnt + 1]); // 새로운 위치&이동 거리를 큐에 추가(이동거리+1)
      visited[ny][nx] = true;
    }
  }

성장 조건 체크

먹은 물고기 수가 현재 아기 상어의 크기와 같아지면 아기 상어의 크기를 증가시킵니다.
더 이상 먹을 수 있는 물고기가 없을 때까지 이 과정을 반복합니다.

//smallq가 비어 있지 않으면, 즉 먹을 물고기가 있다면
//smallq를 정렬하여 가장 가까운 물고기를 선택. 거리, 위쪽, 왼쪽 순으로 정렬
 if (smallq.length) {
    smallq.sort((a, b) => {
      //이동거리 다른지
      if (a[2] !== b[2]) {
        return a[2] - b[2];
      } else {
        // 행 기준
        if (a[0] !== b[0]) {
          return a[0] - b[0];
        } else {
          // 열 기준
          return a[1] - b[1];
        }
      }
    });
	
   //fCnt는 아기 상어가 그 위치까지 이동하는 데 걸리는 거리
    const [fy, fx, fCnt] = smallq[0]; //smallq[0] 가장 우선순위가 높은 물고기
    map[fy][fx] = 0; // 물고기 먹고 0으로 초기화
    eats[lv]++;  // 먹은 물고기 수 증가

    if (eats[lv] === lv) {
      lv++; //자신의 크기만큼 물고기 먹으면 크기 증가
    }
    cur = [fy, fx];  //아기 상어의 현재 위치를 물고기를 먹은 위치로 업데이트
    movCnt = fCnt; //이동 거리를 현재 이동 거리로 업데이트
  } else { //  물고기가 없으면 현재까지의 총 이동 거리를 반환
    return dist;
  }

  dist += movCnt; // 현재 이동 거리를 총 이동 거리에 더함
}

종료 조건

BFS 탐색 결과, 더 이상 먹을 물고기가 없으면 조건문을 빠져나오고 총 이동거리(걸린시간) 출력

전체 코드

// 16236.js

const readFileSyncAddress = '/dev/stdin';
// const readFileSyncAddress = "text.txt";

let input = require("fs")
  .readFileSync(readFileSyncAddress)
  .toString()
  .split("\n");

// 첫째 줄에 수열의 길이 N이 주어진다.
const N = Number(input.shift());

// 둘째 줄에 수열이 주어진다.
const map = input.map((v) => v.split(" ").map(Number));

// 상하좌우 이동방향 정의
const dy = [-1, 0, 0, 1];
const dx = [0, -1, 1, 0];

// 아기 상어의 먹은 물고기수 저장
const eats = new Array(7).fill(0);

function bfs() {
  // 시작 위치 찾기.
  let cur = [];
  let lv = 2;
  let dist = 0;

  for (let i = 0; i < N; i++) {
    for (let j = 0; j < N; j++) {
      if (map[i][j] === 9) {
        cur = [i, j];
        map[i][j] = 0;
      }
    }
  }

  while (true) {
    const q = [];
    const visited = Array.from({ length: N }, () => new Array(N).fill(false));
    // 먹이들의 위치를 담을 배열 => 상, 좌 순으로만 탐색해서는 제대로 비교할 수 없음.
    const smallq = [];
    let movCnt = 0;

   // [y, x, 이동거리]
   q.push([...cur, 0]);

   // 실질적인 BFS
   while (q.length) {
     const [y, x, cnt] = q.shift();

     // 먹이 발견
     if (map[y][x] < lv && map[y][x] !== 0) {
       smallq.push([y, x, cnt]);
     }

     for (let i = 0; i < 4; i++) {
       const ny = y + dy[i];
       const nx = x + dx[i];

       // 범위 체크
       if (ny < 0 || ny >= N || nx < 0 || nx >= N) continue;

       // 물고기 레벨 체크
       if (map[ny][nx] > lv) continue;

       // 방문 체크
       if (visited[ny][nx]) continue;

       q.push([ny, nx, cnt + 1]);
       visited[ny][nx] = true;
     }
  }

  if (smallq.length) {
    // 거리 => 위쪽 => 왼쪽으로 정렬.
    smallq.sort((a, b) => {
      if (a[2] !== b[2]) {
        return a[2] - b[2];
      } else {
        if (a[0] !== b[0]) {
          return a[0] - b[0];
        } else {
          return a[1] - b[1];
        }
      }
    });

    // feed의 위치와 거리
    const [fy, fx, fCnt] = smallq[0];

    map[fy][fx] = 0;
    eats[lv]++;

    // 레벨 업
    if (eats[lv] === lv) {
      lv++;
    }
    cur = [fy, fx];
    movCnt = fCnt;
  }
  // 종료 조건 => 먹이 큐가 빈 경우.
  else {
    return dist;
  }

  dist += movCnt;
}
}


console.log(bfs());
profile
I want to become a UX Developer

0개의 댓글