https://www.acmicpc.net/problem/16946
16946번: 벽 부수고 이동하기 4
N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 한 칸에서 다른 칸으로 이동하려면, 두 칸이 인접해야 한다. 두 칸이
www.acmicpc.net
1. 문제의 유형 및 이해
그래프 탐색
$N*M$ 행렬에 이동할 수 있는 곳을 0으로 벽을 1로 하여 주어진다.
인접한 칸을 이동하려면 두 칸이 인접해 있어야한다.
각각의 벽에 대해 벽을 부쉈을 때 그 위치에서 이동할 수 있는 칸의 개수를 세어보자.
주어지는 input의 크기는 $N<=1000, M<=1000)이다.
2. 문제 접근 방법
1. Naiive
문제에서 가장 Naiive하게 접근을 하는 방법은 각 벽에서 갈 수 있는 모든 곳에 대해 탐색해보는 방법입니다.
이 경우 주어진 행렬의 테두리가 모두 벽이라고 가정했을 때
O(2*(N+M-2) * (N-1)(M-1))로 O($N^2M^2)의 시간 복잡도가 나오게 됩니다.
주어지는 input의 크기와 시간을 고려할 때 시간 초과가 나올 것 같습니다.
2. 나아가보기
시간초과가 나오는 주요한 이유 중 하나는 이미 확인한 곳에 대해 "중복"으로 확인하는데에 있습니다.
관점을 바꿔 벽이 아닌, 갈 수 있는 곳에 대해 grouping을 해두고, 각 벽에서는 인접한 곳에 갈 수 있는 group이 있는지를 확인해봅니다. 중복이 아닌 각 group들에 대해 group에 포함된 개수들을 더해준다면 이미 구한 값을 중복으로 구하게 되는 비효율적인 방법을 제거할 수 있습니다.
위의 예시의 경우 O((N-1)*(M-1)) + O(2*(N+M-2)로 O(NM)시간복잡도로 해결이 가능합니다.
구현의 경우 갈 수 있는 공간에 대해 BFS로 탐색하면서 group을 나누었습니다.
group들에 대해 <groupId, cnt>를 HashMap에 저장해두고, 각각의 벽에 대해 4방향을 탐색하면서 진행하였습니다. 이 때 벽의 경우 Queue를 활용하여 진행했는데, 비슷한 아이디어라도 어떻게 구현하느냐에 따라 시간이 조금씩 달라지는 것 같습니다.
package solvedac.level5.gold2;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
/**
* @문제번호 : Q17143
* @문제이름 : 벽부수고 이동하기 4
* @난이도 : Gold II
* @date : 2022-02-19 오후 4:31
* @author : pcrmcw0486
* @문제이해
* N*M 행렬에서 각각의 벽에 대해
* 벽을 부수고 이동할 수 있는 곳으로 변경해보았을 때 그 위치에서
* 이동할 수 있는 칸의 개수를 출력한다 %10해서
* @알고리즘
* 그래프탐색
* @접근방법
* 세어본것을 또 세어본다는 것 자체가 낭비임.
* 빈 공간들에 대해 중복적으로 검사를 하게 되는데 이 부분이 매우 비효율적이라고 생각함.
* 내 아이디어는 다음과 같음
* - 1. 빈 공간들에 대해 grouping 하고 ID부여
* - 2. 각 벽들은 현재 위치에서 중복되지 않는 ID들에 대해 값을 더해주고
* 자신의 위치 +1을 해줌.
* - O(NM) + 벽개수*4
*/
public class Main {
static int[][] dir = new int[][]{{-1,0},{0,1},{1,0},{0,-1}};
public static void main(String[] args) throws IOException {
Queue<Point> walls = new LinkedList<>();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken());
int M = Integer.parseInt(st.nextToken());
int[][] map = new int[N][M];
for(int i =0;i<N;i++){
String line = br.readLine();
for(int j=0;j<M;j++){
if(line.charAt(j) == '0')
map[i][j] = 1;
else{
map[i][j] = 0;
walls.add(new Point(i, j));
}
}
}
HashMap<Integer, Integer> groupInfo = new HashMap<>();
Queue<Point> group;
int groupId = 2;
for(int i =0;i<N;i++){
for(int j=0;j<M;j++){
if(map[i][j] == 1){
int cnt =0;
group = new LinkedList<>();
map[i][j] = groupId;
group.add(new Point(i,j));
while (!group.isEmpty()) {
Point cur = group.poll();
cnt++;
for(int d =0;d<4;d++){
int nx = cur.x + dir[d][0];
int ny = cur.y + dir[d][1];
if(nx<0 || nx>N-1||ny<0 || ny>M-1) continue;
if(map[nx][ny] == 0) continue;
if(map[nx][ny] ==1) {
map[nx][ny] = groupId;
group.add(new Point(nx, ny));
}
}
}
groupInfo.put(groupId, cnt);
groupId++;
}
}
}
int[][] ans = new int[N][M];
while(!walls.isEmpty()){
Point cur = walls.poll();
Set<Integer> checkGroup = new HashSet<>();
int cnt =0;
for(int i =0;i<4;i++){
int nx = cur.x + dir[i][0];
int ny = cur.y + dir[i][1];
if(nx<0 || nx>N-1||ny<0 || ny>M-1) continue;
if(map[nx][ny] == 0) continue;
if(!checkGroup.contains(map[nx][ny])){
cnt += groupInfo.get(map[nx][ny]);
checkGroup.add(map[nx][ny]);
}
}
ans[cur.x][cur.y] = (cnt+1)%10;
}
StringBuilder sb = new StringBuilder();
for(int i =0;i<N;i++){
for(int j=0;j<M;j++){
sb.append(ans[i][j]);
}
sb.append('\n');
}
System.out.println(sb);
}
static class Point{
int x,y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
}
'알고리즘 > 문제풀이' 카테고리의 다른 글
[백준] Q1806 부분합 JAVA (0) | 2022.03.01 |
---|---|
[백준] Q2623 음악 프로그램 JAVA (0) | 2022.03.01 |
[백준] Q1766 문제집 JAVA (0) | 2022.02.19 |
[백준] Q1202 보석도둑 JAVA (0) | 2022.02.19 |
[백준] Q1007 벡터 매칭 Java (0) | 2022.02.12 |