백준 2573 빙산 - JAVA

2025. 9. 10. 00:36·알고리즘/백준

2573 문제 링크


https://www.acmicpc.net/problem/2573

 

문제 설명


 

입출력


 

코드


<빙산 조각 탐색과 빙산의 녹는 양 계산이 분리된 풀이> - 일반적인 탐색 틀에 맞춘 풀이, 직관적

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.io.*;
import java.util.*;
 
public class Main {
    static int N, M;
    static int[][] map;
    static int[][] temp;
    static boolean[][] visit;
    static int[] dx = {-1, 1, 0, 0};
    static int[] dy = {0, 0, -1, 1};
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st;
        
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        map = new int[N][M];
        visit = new boolean[N][M];
        for (int i = 0; i < N; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < M; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
            } 
        }
        int year = 0;
        while(true) {
            int piece = 0;
            melting();
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < M; j++) {
                    if (visit[i][j] || map[i][j] == 0) continue;
                    dfs(i, j);
                    piece++;
                } 
            }
            year++;
            if (piece >= 2) break; 
            if (piece == 0) {
                year = 0;
                break;
            }
        }
        bw.write(String.valueOf(year));
        bw.flush();
    }
    private static void melting() {
        temp = new int[N][M];
        visit = new boolean[N][M];
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < M; j++) {
                int count = 0;
                for (int d = 0; d < 4; d++) {
                    int ni = i + dx[d];
                    int nj = j + dy[d];
                    if (ni < 0 || nj < 0 || ni >= N || nj >= M) continue;
                    if (map[ni][nj] <= 0) count++;
                } 
                temp[i][j] = map[i][j] - count > 0 ? map[i][j] - count : 0;
            } 
        } 
        for (int i = 0; i < N; i++) {
            map[i] = Arrays.copyOf(temp[i], M);
        } 
    }
    private static void dfs(int x, int y) {
        visit[x][y] = true;
        for (int i = 0; i < 4; i++) {
            int nx = x + dx[i];
            int ny = y + dy[i];
            if (nx < 0 || ny < 0 || nx >= N || ny >= M) continue;
            if (visit[nx][ny] || map[nx][ny] == 0) continue; 
            dfs(nx, ny);
        } 
    }
}
Colored by Color Scripter
cs

 

<빙산 조각 탐색과 빙산의 녹는 양 계산을 동시에 수행하는 풀이> - 효율적인 탐색 풀이, 코드 복잡도 ⬆️

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import java.io.*;
import java.util.*;
 
public class Main {
    static int N, M;
    static int[][] map;
    static int[][] visit;
    static Queue<int[]> queue;
    static int[] dx = {-1, 1, 0, 0};
    static int[] dy = {0, 0, -1, 1};
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st;
        
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        map = new int[N][M];
        visit = new int[N][M];
        for (int i = 0; i < N; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < M; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
            } 
        }
        int year = 0;
        while(countIsland() == 1) {
            int zeroCount = 0;
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < M; j++) {
                    // 미리 계산된 visit을 활용해 다음 맵 갱신
                    if (visit[i][j] != -1) map[i][j] = Math.max(map[i][j] - visit[i][j], 0);
                    if (map[i][j] == 0) zeroCount++; 
                } 
            }
            // 모두 녹아버리면 year는 0으로
            if (zeroCount == N * M) {
                year = 0;
                break;
            } 
            year++;
        }
        bw.write(String.valueOf(year));
        bw.flush();
    }
    private static int countIsland() {
        int count = 0;
        for (int i = 0; i < N; i++) {
            Arrays.fill(visit[i], -1);
        }
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < M; j++) {
                if (map[i][j] == 0 || visit[i][j] != -1) continue;
                bfs(i, j);
                count++;
            } 
        } 
        return count;
    }
    private static void bfs(int x, int y) {
        queue = new ArrayDeque<>();
        queue.offer(new int[] {x, y});
        visit[x][y] = countSea(x, y);
        while(!queue.isEmpty()) {
            int[] cur = queue.poll();
            for (int i = 0; i < 4; i++) {
                int nx = cur[0] + dx[i];
                int ny = cur[1] + dy[i];
                if (nx < 0 || ny < 0 || nx >= N || ny >= M) continue;
                if (visit[nx][ny] != -1 || map[nx][ny] == 0) continue; 
                visit[nx][ny] = countSea(nx, ny); // 다음 번 맵 변경 시 얼마나 빼야하는지 계산
                queue.offer(new int[] {nx, ny});
            } 
        }
    }
    private static int countSea(int x, int y) {
        int count = 0;
        for (int i = 0; i < 4; i++) {
            int nx = x + dx[i];
            int ny = y + dy[i];
            if (nx < 0 || ny < 0 || nx >= N || ny >= M) continue;
            if (map[nx][ny] != 0) continue;
            count++; 
        } 
        return count;
    }
}
Colored by Color Scripter
cs

 

알고리즘 설명


설명에 앞서, 주어진 문제를 두 번째 풀이로 접근했다면 컴퓨팅 사고에 최적화된 방식으로 사고했다고 말씀드리고 싶습니다.

저는 첫 번째 풀이로 생각보다 수월하게 문제를 해결했고, 대부분의 사람들이 그렇듯 탐색 알고리즘은 일정한 유형에 맞춰 풀린다고 여겨왔습니다. 그런데 백준 채점현황을 보다가 두 번째 풀이를 접하면서 제 사고의 틀이 깨지는 경험을 하게 되었습니다.

 

해당 문제는 BFS던 DFS던 모든 배열의 원소를 반복문으로 순회해야 하기 때문에 효율성의 차이는 크게 나지 않는다고 볼 수 있습니다. 굳이 따지자면 N * M이 최대로 커지는 경우 DFS가 300 * 300으로 재귀의 깊이가 9만 번까지 내려갈 수 있으므로, BFS가 오버플로우에서 안전하다... 정도가 될 수 있겠네요.

 

그러면 이제 제가 쉽게 풀었던 첫 번째 문제 풀이를 설명드리겠습니다. 첫 번째 풀이의 전반적인 흐름은 익히 알듯이 일반적인 탐색 풀이 틀에 맞추어 푸는 방식입니다. 조금 더 자세하게 설명드리면, 각 빙산 칸은 4방향의 바다 칸 수만큼씩 녹습니다. 문제는 탐색하면서 map을 바로 갱신하면, 앞서 처리한 칸이 이미 줄어든 영향으로 아직 처리하지 않은 이웃 칸의 바다 개수 계산에 영향을 주어 의도와 다른 결과가 나올 수 있다는 점입니다. 따라서 temp[][] 배열을 선언하고, melting()메서드에서 temp[][] 배열에 map[][] 배열의 다음 해의 빙산 높이를 저장하고, 루프가 끝난 뒤에 다시 temp[][] 배열을 map[][] 배열에 다시 입혀주어, map[][] 배열을 갱신하고 갱신된 map[][] 배열을 통해

빙산의 개수가 처음으로 2개 이상이 되는 햇수를 찾는 방식입니다.

 

두 번째 풀이입니다. 두 번째 풀이는 제가 혼자서 푼 건 아니지만, 나름 완벽하게 이해했기 때문에 적어보겠습니다.

두 번째 풀이는 visit[][] 배열을 탐색을 위한 방문에만 활용하는 것이 아닌 다음 해의 빙산이 얼마나 녹을지 높이도 기록합니다. 따라서 boolean이 아닌 int형으로 배열을 선언하고 각 원소를 -1 (방문하지 않음)로 설정합니다. BFS로 연결된 덩어리를 탐색하면서, 방문하지 않은 칸이면 visit[x][y]에 countSea(x, y)를 기록하고 큐에 넣습니다. 이렇게 하면 탐색과 동시에 다음 해 상태를 countSea()를 통해 계산할 수 있어, temp 배열을 별도로 만들 필요가 없습니다. BFS가 끝나면 visit 배열을 참조해 map 배열을 한 번에 갱신하고, 이후 덩어리 개수를 세어 빙산이 처음으로 두 덩어리 이상으로 나뉘는 시점을 판단합니다.

해당 풀이의 핵심은 탐색과 다음 해의 빙산이 녹는 양 계산을 한 번의 순회로 처리하여 불필요한 반복을 제거하는 것 입니다.

 

끝으로 풀이 별 메모리 사용량과 실행시간을 확인하며 마치겠습니다. 확실히 visit[][] 배열을 두 가지 용도로 사용하며 재활용까지 한 풀이가 꽤나 실행시간이나 메모리적으로 효율적인 풀이라는 것을 알 수 있습니다.

아래부터 <탐색과 녹는양 계산 따로한 풀이>, <탐색과 녹는양 계산을 동시에 한 풀이>

 

이런 효율적인 풀이들을 볼 때 마다, 아직 갈 길이 참 멀구나... 느낍니다. 뭐 저도 나쁘게 푼 건 아니지만 ㅎㅎ...

 

코드 해석


두 번째 풀이 방식을 기준으로 설명합니다.

먼저 map[N][M] 배열에 각 칸의 빙산 높이를 저장하고, 반복문을 통해 빙산이 두 개 이상으로 나뉘거나 모두 녹을 때까지 매 해를 진행합니다. 각 해에서는 먼저 countIsland() 메서드를 호출해 BFS로 연결된 빙산 덩어리 수를 센 뒤, 각 빙산 칸의 주변 4방향 바다 칸 개수를 계산하여 visit[][]에 기록하고, 이를 이용해 다음 해의 빙산 높이를 map[][]에 갱신합니다. BFS에서는 큐에 현재 칸 좌표를 넣고 꺼내면서 상하좌우 인접 칸을 확인하며, 이미 방문했거나 바다인 칸은 건너뛰고 아직 방문하지 않은 빙산 칸만 큐에 넣어 탐색합니다. 반복문이 끝나면 갱신된 map[][]을 기준으로 다시 countIsland()를 통해 덩어리 수를 확인하고, 빙산이 두 덩어리 이상이면 종료하며 그 해 수를 출력하고, 모든 빙산이 녹으면 0을 출력합니다.

 

핵심은 countIsland()와 bfs()를 통해 덩어리 수 확인과 다음 해 녹는 양 계산을 동시에 수행하고, visit[][] 배열을 활용해 반복문과 계산을 최소화함으로써 효율적인 탐색을 구현하는 것입니다.

'알고리즘 > 백준' 카테고리의 다른 글

백준 1019 책 페이지 - JAVA  (0) 2025.10.20
백준 1941 소문난 칠공주 - JAVA  (0) 2025.10.16
백준 2042 구간 합 구하기 - JAVA  (2) 2025.08.28
백준 17142 연구소 3 - JAVA  (2) 2025.08.23
백준 15903 카드 합체 놀이 - JAVA  (0) 2025.08.20
'알고리즘/백준' 카테고리의 다른 글
  • 백준 1019 책 페이지 - JAVA
  • 백준 1941 소문난 칠공주 - JAVA
  • 백준 2042 구간 합 구하기 - JAVA
  • 백준 17142 연구소 3 - JAVA
BIS's tech Blog
BIS's tech Blog
Welcome to Kanghyun's tech blog :)
  • BIS's tech Blog
    벼익숙의 기술 블로그
    BIS's tech Blog
  • 전체
    오늘
    어제
    • 분류 전체보기 (67) N
      • 알고리즘 (53) N
        • 백준 (44) N
        • 프로그래머스 (9)
      • CS 스터디 (14)
        • CS - JAVA (11)
        • CS - DB (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • GitHub
  • 공지사항

  • 인기 글

  • 태그

    bottom-up
    java
    깊이 우선 탐색
    알고리즘 고득점 kit
    동적 계획
    너비 우선 탐색
    프로그래머스
    Baekjoon
    브루트포스
    완전탐색
    BFS
    기술질문
    Top-Down
    백준
    DFS
    dp
    CS
    Lv2
    자료구조
    재귀
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
BIS's tech Blog
백준 2573 빙산 - JAVA
상단으로

티스토리툴바