알고리즘/그래프
[BOJ] 백준 16236 - 아기상어 풀이
송승현(SSH)
2023. 3. 12. 22:03
1. 문제
https://www.acmicpc.net/problem/16236
16236번: 아기 상어
N×N 크기의 공간에 물고기 M마리와 아기 상어 1마리가 있다. 공간은 1×1 크기의 정사각형 칸으로 나누어져 있다. 한 칸에는 물고기가 최대 1마리 존재한다. 아기 상어와 물고기는 모두 크기를 가
www.acmicpc.net
2. 풀이 Point
<Error Point>
- 우선순위에 대하여 정렬하는 방법을 찾지 못함(먹이 큐를 따로 둔다는 것을 생각 못함)….
- BFS를 한 번만 돌고 판단하려함 → 먹이를 먹고 난 후 우선 순위가 달라진다는 것을 생각 못함
- 먹이를 먹고 다시 탐색하려 할 때, 처음 상어가 있었던 위치도 다시 탐색해줘야 하는데, 이를 bfs에서 조건을 걸어주려함 → 상어가 있는 위치는 9인데, 이를 0으로 바꿔주지 않고 bfs에서 9에 대한 조건도 걸어주려함(map[xf][yf] != 9)
<풀이 Point>
- 상어에 대한 객체 정의를 한다.
- 입력값을 받을 때 상어의 위치를 미리 객체로 만들고 시작한다. 이때 상어가 있던 위치를 0으로 만든다.
- 무한 반복을 돌면서 우선 순위 큐를 활용한 bfs 탐색을 실행(먹이 리스트가 나옴) → 먹이를 먹는 함수 실행 → 먹이를 먹고 난 후 먹이 리스트 큐를 초기화 해주고 먹은 위치에서 다시 bfs 탐색을 실행하여 먹이 리스트 갱신
- 먹을 수 있는 먹이가 없다면 무한 반복 탈출
3. 소스 코드
package bfs;
import java.io.*;
import java.util.*;
public class babyShark {
static int N;
static int[][] map;
static int[] dx = {1, -1 , 0, 0};
static int[] dy = {0, 0 , -1, 1};
static int result = 0; // 이동하는데 걸린 시간
static class Point {
int x;
int y;
int size; // 상어의 사이즈
int cnt; // 먹은 물고기의 개수
int move; // 이동한 거리
public Point(int x, int y, int size, int cnt, int move) {
this.x = x;
this.y = y;
this.size = size;
this.cnt = cnt;
this.move = move;
}
}
static PriorityQueue<Point> pq; // bfs 한바퀴를 돌았을 때 먹을 수 있는 먹이를 저장한 큐
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
N = Integer.parseInt(br.readLine());
map = new int[N][N];
Point shark = null;
for (int i = 0; i < N; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
for (int j = 0; j < N; j++) {
map[i][j] = Integer.parseInt(st.nextToken());
if (map[i][j] == 9) {
shark = new Point(i, j, 2, 0, 0);
map[i][j] = 0; // 상어가 있는 자리를 0으로 치환(나중에 탐색할 수 있으므로)
}
}
}
babySharkSearch(shark);
bw.write(Integer.toString(result));
bw.flush();
bw.close();
br.close();
}
// 먹이를 찾는 bfs
private static void babySharkSearch(Point shark) {
// 먹이들의 위치를 저장하는 큐
pq = new PriorityQueue<>(new Comparator<Point>() {
@Override
public int compare(Point o1, Point o2) {
// 거리가 같다면
if (o1.move == o2.move) {
// 거리가 같다면 위쪽에 있는 먹이를 먹어야 하므로 x값을 비교
// 그 x값이 같다면 가장 왼쪽에 있는 먹이를 먹어야 하므로 y값이 작은것으로 정렬
if (o1.x == o2.x) {
return o1.y - o2.y;
} else {
return o1.x - o2.x;
}
} else {
return o1.move - o2.move;
}
}
});
// bfs를 한번 탐색하면 현재의 상어 위치에서 탐색할 수 있는 먹이들이 pq에 들어간다.
// 만약 먹이를 먹었다면 그 위치에서부터 다시 나머지 먹이들을 탐색해줘야한다.
// bfs 탐색에 이용되는 큐
Queue<Point> q = new LinkedList<>();
q.add(shark);
while (true) {
boolean[][] visited = new boolean[N][N];
while (!q.isEmpty()) {
Point poll = q.poll();
if (!visited[poll.x][poll.x]) {
visited[poll.x][poll.y] = true;
}
for (int i = 0; i < 4; i++) {
int xf = poll.x + dx[i];
int yf = poll.y + dy[i];
if (xf >= 0 && xf < N && yf >= 0 && yf < N && !visited[xf][yf] && map[xf][yf] <= poll.size) {
// 먹이를 먹을 수 있는 경우(현재 상어의 크기가 먹이보다 클 경우) 먹이리스트(pq)에 저장
if (map[xf][yf] < poll.size && map[xf][yf] != 0) {
pq.add(new Point(xf, yf, poll.size, poll.cnt + 1, poll.move + 1));
}
visited[xf][yf] = true;
q.add(new Point(xf, yf, poll.size, poll.cnt, poll.move + 1));
}
}
}
// 더이상 먹을 수 있는 먹이가 없다면
if (pq.isEmpty()) {
break;
}
// bfs 한바퀴를 돌았다면 (현재 상어의 위치에서 먹을 수 있는 먹이를 우선순위에 맞게 서치했다면) 먹이 함수 실행
// 먹이 함수가 끝난 후 그 위치부터 다시 탐색해야 하므로 bfs 큐(q)에 넣는다.
q.add(eat());
// 다시 탐색해야 하므로 먹이 큐 초기화
pq.clear();
} // while(true)
} // babySharkSearch
// 먹이를 먹는 함수
// 하나만 꺼내는 이유 : 먹이를 먹고 다시 우선순위에 따라 탐색해줘야 하기 때문에
// 그렇다면 bfs를 돌 때 먹이를 찾으면 바로 bfs를 종료시키는 건 안되는가? : 먹이들의 우선순위를 찾아야하기 때문
private static Point eat() {
Point poll = pq.poll();
// 크기가 2인 상어는 2마리의 물고기를 먹어야 크기가 +1 되므로
if (poll.size == poll.cnt) {
poll.size++;
poll.cnt = 0;
}
// 이 먹이를 먹을 때까지 이동한 거리 값을 합함
result += poll.move;
// 그 위치의 먹이를 먹었으므로 map에서 0으로 만들어줘야함
map[poll.x][poll.y] = 0;
return new Point(poll.x, poll.y, poll.size, poll.cnt, 0);
}
}
- 두 번째로 푼 코드
import java.io.*;
import java.util.*;
import java.lang.*;
public class Main {
static int N;
static int[][] map;
static class Shark {
int x;
int y;
int weight;
int cnt;
int dist;
public Shark(int x, int y, int weight, int cnt, int dist) {
this.x = x;
this.y = y;
this.weight = weight;
this.cnt = cnt;
this.dist = dist;
}
}
static PriorityQueue<Shark> pq;
static Shark my;
static int[] dx = {1, -1, 0, 0};
static int[] dy = {0, 0, 1, -1};
static boolean[][] visited;
static int result = 0;
static BufferedReader br;
static BufferedWriter bw;
public static void main(String[] args) throws IOException {
br = new BufferedReader(new InputStreamReader(System.in));
bw = new BufferedWriter(new OutputStreamWriter(System.out));
N = Integer.parseInt(br.readLine());
map = new int[N][N];
for (int i = 0; i < N; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
for (int j = 0; j < N; j++) {
map[i][j] = Integer.parseInt(st.nextToken());
if (map[i][j] == 9) {
my = new Shark(i, j, 2,0, 0);
map[i][j] = 0; // 상어가 출발하고 나면 그 위치를 0으로 바꿔줘야 이동할 수 있음
}
}
}
// 상어 이동 스타트
moveBabyShark();
bw.write(Integer.toString(result));
bw.flush();
bw.close();
br.close();
}
private static void moveBabyShark() throws IOException {
while (true) {
// 우선순위인 물고기 찾기
pq = new PriorityQueue<>(new Comparator<Shark>() {
@Override
public int compare(Shark o1, Shark o2) {
if (o1.dist == o2.dist) {
if (o1.x == o2.x) {
return o1.y - o2.y;
}
return o1.x - o2.x;
}
return o1.dist - o2.dist;
}
});
visited = new boolean[N][N];
searchFishBFS();
// 먹을 수 있는 물고기가 없음
if (pq.isEmpty()) {
break;
} else {
my = eatFish();
}
}
}
// 물고기 찾기 BFS
private static void searchFishBFS() {
Queue<Shark> q = new ArrayDeque<>();
q.add(my);
visited[my.x][my.y] = true;
while (!q.isEmpty()) {
Shark poll = q.poll();
for (int i = 0; i < 4; i++) {
int xf = poll.x + dx[i];
int yf = poll.y + dy[i];
if (xf >= 0 && xf < N && yf >= 0 && yf < N && !visited[xf][yf]) {
// 먹을 수 있는 물고기 발견
if (map[xf][yf] != 0 && poll.weight > map[xf][yf]) {
pq.add(new Shark(xf, yf, poll.weight, poll.cnt,poll.dist + 1));
} else if (map[xf][yf] != 0 && poll.weight < map[xf][yf]) {
continue;
}
// map[xf][yf] == 0이거나, 물고기가 있을 때 내 몸무게보다 같을 때
visited[xf][yf] = true;
q.add(new Shark(xf, yf, poll.weight, poll.cnt,poll.dist + 1));
}
}
}
}
// 물고기를 먹는 함수
private static Shark eatFish() {
Shark poll = pq.poll();
// 먹었다
poll.cnt += 1;
map[poll.x][poll.y] = 0;
result += poll.dist;
poll.dist = 0;
if (poll.cnt == poll.weight) {
poll.weight += 1;
poll.cnt = 0;
}
return poll;
}
}
4. 배운점
- 우선순위 큐를 다시 한번 되새기는 계기가 됨
- 모든 문제가 bfs, 혹은 dfs 한 번으로 끝날 수 있다는 고정관념 버리기 → 어떤 객체가 우선순위에 따라 특정 위치에 가고 나서 다시 우선순위를 측정하려할 때 bfs나 dfs 등을 여러 번 실행하는 경우를 생각하자.
5. 점수
30/100