일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- Java
- SWEA
- 유니온파인드
- 항해플러스ai
- 코테
- DFS
- 코딩테스트
- 알고리즘
- 다시보기
- Spring
- 완전탐색
- 코드트리
- SSAFY
- 항해솔직후기
- 싸피
- 자바
- DP
- Union Find
- 다익스트라
- 그래프
- JPA
- 항해플러스ai후기
- database
- 알고리즘기본개념
- 트러블슈팅
- 그리디
- 코딩테스트실력진단
- JUnit
- BFS
- 백준
- Today
- Total
HwangHub
[Java/Simulation] 백준 16236. 아기상어 본문
🤔 Intuition
시뮬레이션 문제인 것은 확실하나, 탐색을 어떻게 할지 고민하였다.
우선되는 먹이의 조건이 (1) 거리가 가까울 것 (2) row가 작을 것 (3) column이 작을 것 이었으므로 델타탐색을 "상 좌 우 하" 로 하면서 가장 먼저 나오는 먹이를 낼름 먹어버리면 될 것으로 봤다.
하지만 위는 틀렸다.
위의 경우에는 항상 거리값이 가장 가까운 먹이를 찾을 수 있지만, 바로 만난 먹이가 가장 위에 있고, 가장 왼쪽에 있다는 것을 보장할 수 없다. 아래 예시를 보면 이해가 가능하다.
private static int[] dy = {-1, 0, 0, 1};
private static int[] dx = {0, -1, 1, 0};
. . 1 . .
. 2 x 3 .
4 x C x 6
. 5 x 7 .
. . 8 . .
C 지점에서 상 좌 우 하 방식으로 탐색을 했을 때, 2번째 칸들의 탐색 순서를 위와 같이 나타낼 수 있다. 여기서 포인트는 4번 뒤에 6번이 5번이 되어야 하지만, 탐색 순서상 아래 좌표가 우선적으로 탐색되게 된다는 걸 알 수 있다.
따라서 이를 해결하기 위해서 가장 단순한 방법은 우선순위 큐를 이용하는 것이다. BFS는 어차피 큐를 이용하니까, 조건에서 안내하는 우선순위를 우선순위큐의 정렬조건으로 세팅해 준 뒤 이를 이용하여 BFS 탐색을 하는 방법이 가장 직관적이다.
🔎 Algorithm & Complexity
* @algorithm simulation (bfs)
* @time O(N^2 * log(N^2)) : 우선순위큐 BFS 시 맵의 모든 칸이 큐에 들어가면서 O(N^2) * 우선순위큐 연산시 O(logN^2) -> 144 ms
* @memory O(N^2) -> 14572 KB
👨🏻💻 Logic
상어 위치로부터 BFS 탐색을 통해 target을 찾는다. 이 과정에서 우선순위 큐를 이용하여 문제에서 제시하는 우선순위 조건을 준수한다.
*** 여기서 본인은 처음에 visited 체크를 Queue에서 poll할 때 해줬었다. 이렇게 로직을 구성했을 때에는 Memory Limit Exceeded 가 발생했다. 그 이유는 Queue 자료구조에서 해당 좌표가 뽑힐 때까지 visited 처리를 하지 않아서, 다른 좌표에서도 해당 좌표를 갈 수 있다고 판단하고 queue에 계속 중복된 좌표를 담아내게 되기 때문이다. 특히나 우선순위 큐로 구성하게 되면 더더욱 중복이 많이 발생할 수 있게 된다. 따라서 BFS를 사용할 때에는 반드시 push할 때 visited를 체크해줘야 한다. (평소에 이렇게 하지 않긴 했는데, 왜 그렇게 해왔는지 이유는 몰랐던 것 같다. 이번 기회에 생각해볼 수 있는 계기가 되어서 좋았다.)
public class BOJ_16236_아기상어 {
private static int N, ans, cnt, sharkSize, sharkY, sharkX;
private static int[][] map;
private static boolean hasTarget;
private static int[] dy = {-1, 0, 0, 1};
private static int[] dx = {0, -1, 1, 0};
private static int[] visited;
private static int[][] step;
private static Queue<int[]> q = new PriorityQueue<>(((o1, o2) -> {
if (o1[2] == o2[2]) {
if (o1[0] == o2[0]) {
return o1[1] - o2[1];
}
return o1[0] - o2[0];
}
return o1[2] - o2[2];
}));
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringBuilder sb = new StringBuilder();
StringTokenizer st;
N = Integer.parseInt(br.readLine());
map = new int[N][N];
step = new int[N][N];
int num;
for (int i = 0; i < N; i++) {
st = new StringTokenizer(br.readLine());
for (int j = 0; j < N; j++) {
num = Integer.parseInt(st.nextToken());
if (num == 9) {
sharkY = i;
sharkX = j;
continue;
}
map[i][j] = num;
}
}
sharkSize = 2;
do {
hasTarget = bfs();
} while (hasTarget);
br.close();
bw.write(sb.append(ans).toString());
bw.flush();
bw.close();
}
private static boolean bfs() {
q.clear();
q.add(new int[]{sharkY, sharkX, step[sharkY][sharkX]});
visited = new int[N];
visited[sharkY] |= 1 << sharkX;
while (!q.isEmpty()) {
int[] now = q.poll();
int y = now[0];
int x = now[1];
if (map[y][x] != 0 && map[y][x] < sharkSize) { // 잡아먹을 수 있는 경우
cnt++;
if (cnt == sharkSize) {
sharkSize++;
cnt = 0;
}
map[y][x] = 0;
ans = step[y][x];
sharkY = y;
sharkX = x;
return true;
}
for (int i = 0; i < 4; i++) {
int ny = y + dy[i];
int nx = x + dx[i];
if (canGo(ny, nx)) {
step[ny][nx] = step[y][x] + 1;
visited[ny] |= 1 << nx;
q.add(new int[]{ny, nx, step[ny][nx]});
}
}
}
return false;
}
private static boolean canGo(int y, int x) {
return inRange(y, x) && map[y][x] <= sharkSize && (visited[y] & (1 << x)) == 0;
}
private static boolean inRange(int y, int x) {
return y >= 0 && y < N && x >= 0 && x < N;
}
}
🔥Other's insight
다른 사람은 우선순위 큐를 굳이 사용하지 않고, BFS 탐색을 매번 쭉 돌면서 먹잇감까지의 거리와 좌표를 갱신해주면서 진행하였다. 진정으로 완탐을 진행하였지만, 오히려 우선순위 큐를 사용할 때 발생하는 불필요한 정렬 연산이 들어가지 않아 실행시간이 더욱 단축되었다.
사실 위 로직보다 아래 로직이 더 군더더기 없고 명료한 코드로 느껴진다. 이 코드를 보니, 내가 평소에 너무 돌아가게 생각하고 있는 건 아닌지 반성하게 된다.
/**
* @time 88 ms
* @memory 12280 KB
*/
public class Main {
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StringBuilder sb = new StringBuilder();
static StringTokenizer st;
static int N;
static int[][] board;
static BabyShark babyShark;
static int eatCnt;
static int ans;
static int[][] dir = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
static boolean check_range(int y, int x) {
return y >= 0 && x >= 0 && y < N && x < N;
}
static boolean bfs() {
Queue<Node> q = new ArrayDeque<Node>();
q.add(new Node(babyShark.y, babyShark.x, 0));
board[babyShark.y][babyShark.x] = 0;
boolean [][]visited = new boolean[N][N];
visited[babyShark.y][babyShark.x] = true;
boolean eat = false;
int eatY = 0, eatX = 0;
int minDist = Integer.MAX_VALUE;
while(q.size() > 0) {
Node cNode = q.poll();
int y = cNode.y;
int x = cNode.x;
int depth = cNode.depth;
if(eat && minDist < depth) {
break;
}
// 먹을 수 있는 먹이라면
if(board[y][x] > 0 && board[y][x] < babyShark.size) {
if(!eat) {
eat = true;
minDist = depth;
eatY = y;
eatX = x;
}else {
if(y < eatY || (y == eatY && x < eatX)) {
eatY = y;
eatX = x;
}
}
continue;
}
for(int i = 0; i < 4; i++) {
int ny = y + dir[i][0];
int nx = x + dir[i][1];
// 현재 상어 사이즈보다 작다면
if(check_range(ny, nx) && !visited[ny][nx] && board[ny][nx] <= babyShark.size) {
visited[ny][nx] = true;
q.add(new Node(ny, nx, depth + 1));
}
}
}
if(eat) {
eatCnt++;
ans += minDist;
babyShark.y = eatY;
babyShark.x = eatX;
if(eatCnt == babyShark.size) {
eatCnt = 0;
babyShark.size++;
}
return true;
}
return false;
}
public static void main(String[] args) throws NumberFormatException, IOException {
N = Integer.parseInt(br.readLine());
board = new int[N][N];
for(int i = 0; i < N; i++) {
st = new StringTokenizer(br.readLine());
for(int k = 0; k < N; k++) {
board[i][k] = Integer.parseInt(st.nextToken());
if(board[i][k] == 9) {
babyShark = new BabyShark(i, k, 2);
}
}
}
while(true) {
if(!bfs()) break;
}
System.out.println(ans);
}
static class Node{
int y;
int x;
int depth;
Node(int y, int x, int depth){
this.y = y;
this.x = x;
this.depth = depth;
}
}
static class BabyShark{
int y;
int x;
int size;
BabyShark(int y, int x, int size){
this.y = y;
this.x = x;
this.size = size;
}
}
}
'workspace > algorithm' 카테고리의 다른 글
[Java/BFS] 소프티어. 안전운전을 도와줄 차세대 지능형 교통시스템 (2) | 2024.03.19 |
---|---|
[Java/BFS] 소프티어. 나무섭지 (0) | 2024.03.14 |
[Java/BruteForce] 소프티어. 함께하는효도 (0) | 2024.03.13 |
[Java/BruteForce] 백준 2116. 주사위쌓기 (0) | 2024.03.12 |
[Java/Stack] 프로그래머스. 과제 진행하기 (0) | 2024.03.12 |