코테/백준

[백준/JAVA] 16946번: 벽 부수고 이동하기 4

imname1am 2024. 2. 8. 17:50
반응형

📖 문제

 

16946번: 벽 부수고 이동하기 4

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 한 칸에서 다른 칸으로 이동하려면, 두 칸이 인접해야 한다. 두 칸이

www.acmicpc.net

 

 

 

💡  풀이 방식

• BFS

필요 자료구조
- 2차원 int형 배열 group : 0끼리 그룹화된 배열 정보 저장
- Map<그룹 번호, 해당 그룹에 속한 0 수>각각 다른 그룹으로 나눌 수 있다 (서로소 = 분리 집합)

 

- BFS 통해 그룹 생성 시, 이미 만들어진 그룹에 속한 인덱스는 제외하고 탐색을 진행한다.

- 벽(1)인 경우에만 본인 칸과 주변 상하좌우에 존재하는 0의 개수를 센다. (BFS 아니고 탐색 단 한 번)

 

 

 

 

💥 유의사항

N과 M이 각각 최대 1000이므로 BFS를 여러번 돌리면 시간 초과가 뜬다.

 

 

 

🔺 코드

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import java.util.*;
import java.io.*;
 
public class Main {
    static int[] dx = {-1,1,0,0};
    static int[] dy = {0,0,-1,1};
    
    static int N,M;
    static int[][] map, group;
    
    static Map<Integer, Integer> groupSize = new HashMap<>();   // 🔔 (그룹 번호, 그룹에 속한 0의 수)
    
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");
        
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        
        map = new int[N][M];
        group = new int[N][M]; // 🔔 속한 그룹 정보 저장용 배열
        
        for(int i = 0 ; i < N ; i++) {
            String str = br.readLine();
            for(int j = 0 ; j < M ; j++) {
                map[i][j] = str.charAt(j) - '0';
            }
        }
        
        // 🔔 각 그룹마다 다른 key 갖게 함 (서로소, 분리 집합)
        int groupCnt = 1;
        for(int i = 0 ; i < N ; i++) {
            for(int j = 0 ; j < M ; j++) {
                if(map[i][j] == 0 && group[i][j] == 0) { // 벽이 아니고, 그룹이 결정되지 않은 칸에 대해 bfs 수행
                    groupSize.put(groupCnt, bfs(i, j, groupCnt));
                    groupCnt++;
                }
            }
        }
        
        print();
    }
    
    // 벽에 맞닿은 그룹 합 계산 함수
    private static int count(int x, int y) {
        int cnt = 1; // 본인 칸 포함
        if(map[x][y] == 0)  return 0;
        
        Set<Integer> set = new HashSet<>(); // 현재 칸과 맞닿은 그룹 수 저장용 Set
        
        for(int d = 0 ; d < 4 ; d++) {
            int nx = x + dx[d];
            int ny = y + dy[d];
            
            if(!inRange(nx, ny) || group[nx][ny] == 0)  continue;
            
            // 맞닿은 그룹이 중복일 경우를 위해 Set에 저장
            set.add(group[nx][ny]);
        }
        
        for(int size : set) {
            cnt += groupSize.get(size);
        }
        
        return cnt % 10;
    }
    
    // 그룹마다 0이 몇 개인지 확인하는 메소드
    private static int bfs(int x, int y, int groupCnt) {
        int cnt = 1;
        
        Queue<int[]> q = new ArrayDeque<>();
        q.add(new int[]{x, y});
        group[x][y] = groupCnt; // 그룹 번호 지정
        
        while(!q.isEmpty()) {
            int[] now = q.poll();
            
            for(int d = 0 ; d < 4 ; d++) {
                int nx = now[0+ dx[d];
                int ny = now[1+ dy[d];
                
                // 격자 범위 내에 있고, 그룹에 속해 있지 않고, 벽이 아니어야 이동 가능
                if(canGo(nx, ny)) { // 
                    group[nx][ny] = groupCnt;   // 그룹에 속하게 하기
                    cnt++;
                    q.add(new int[]{nx, ny});
                }
            }
        }
        
        return cnt;
    }
    
    // 격자 범위 내에 있고, 그룹에 속해 있지 않고, 벽이 아니어야 이동 가능
    private static boolean canGo(int x, int y) {
        return (inRange(x, y) && group[x][y] == 0 && map[x][y] == 0);
    }
    
    private static boolean inRange(int x, int y) {
        return (0 <= x && x < N && 0 <= y && y < M);
    }
    
    // 출력 함수
    private static void print() {
        StringBuilder sb = new StringBuilder();
        for(int i = 0 ; i < N ; i++) {
            for(int j = 0 ; j < M ; j++) {
                if(group[i][j] == 0)  {
                    sb.append(count(i, j)+"");
                    continue;
                }
                sb.append(0+"");
            }
            sb.append("\n");
        }
        
        System.out.println(sb.toString());
    }
}
 
cs

 

 

 


💦 어려웠던 점

- N과 M의 크기가 1000이나 되므로 벽에서마다 bfs를 수행하면 시간초과가 당연히 뜰 것이라고 생각해야 했는데 그러지 못 했다. 개선하자!

 

 

🧐 새로 알게 된 내용

분리 집합 : BFS 비용 최소화를 위해 그루핑을 먼저 진행하는 것 (참고)

 

 

1회독 2회독 3회독 4회독 5회독
V        

(참고)

✔ 상세한 그림을 포함한 설명들 덕분에 이해하는 시간을 단축할 수 있었다. 감사합니다!!

 

[백준 16946] 벽 부수고 이동하기 (java)

 

technote-mezza.tistory.com

 

 

[백준/자바] 16946 벽 부수고 이동하기 4

문제 N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 한 칸에서 다른 칸으로 이동하려면, 두 칸이 인접해야 한다. 두

settembre.tistory.com

 

반응형