https://www.acmicpc.net/problem/3197
3197번: 백조의 호수
입력의 첫째 줄에는 R과 C가 주어진다. 단, 1 ≤ R, C ≤ 1500. 다음 R개의 줄에는 각각 길이 C의 문자열이 하나씩 주어진다. '.'은 물 공간, 'X'는 빙판 공간, 'L'은 백조가 있는 공간으로 나타낸다.
www.acmicpc.net
코드설명
BFS와 유니온파인드 문제입니다.
처음에 시간복잡도를 고려하지 않고, 단순히 풀었습니다.
하지만, 단순히 시간복잡도를 고려하지않고 BFS를 활용해 푼다면,
1. 빙판이 물에 의해 녹는 것을 검사하면서 O(R*C)의 시간복잡도
2. 각각의 백조가 연결되어있는지 확인하기 위해 검사하는 O(R*C)의 시간복잡도로
총 O( R*C*R*C )의 시간복잡도가 걸립니다.
위의 시간복잡도를 유니온파인드로 대체할경우 O( R*C*m )의 시간복잡도로 변화하기에 통과할 수 있습니다.
문제로직입니다.
- 유니온파인드로 물의 부분을 그룹화시키고, 물에 녹을 다음 빙판의 위치를 저장합니다.
- 첫번째 백조와 두번째 백조가 같은 집합에 속할때까지 계속해서 아래의 로직을 반복합니다.
- 물에 녹을 다음 빙판의 위치를 돌면서
- 만약 '.'와 'L'를 만날경우 집합을 찾습니다.(unionParent)
- 만약 'X'를 만날경우 다음 waterNExtQ에 넣어서 다음 물로 변경시킵니다.
- 물에 녹을 다음 빙판의 위치를 돌면서
문제에서 유의해야할점은, BFS를 진행하면서 바로바로 arr[][] X를 '.'로 변경시키는것이 아닌 해당 로직에서 값에 영향을 주지 않도록 다음 BFS에서 값을 변경시킨다는것입니다.
또한, waterNextQ를 활용하여 현재 Q에서는 현재 Q에서의 연산만 실행하도록 처리합니다.
이렇게 한턴에서 진행한 연산을 독립적으로 처리하기 위해 각 턴마다 연산한 결과를 waterQ = waterNextQ로 얕은복사시켜주어 이후에 waterQ가 사용하도록 처리합니다.
코드
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.*; import java.util.regex.Pattern; public class Main { public static int R, C; public static char[][] arr; public static int answer = 0; //상하좌우 public static int[] dx = {-1,1,0,0}; public static int[] dy = {0,0,-1,1}; public static int[] parent; public static boolean[][] visited; public static Node firstSwan = null; public static Node secondSwan = null; //특정 원소가 속한 집합을 찾습니다. public static int findParent(int x) { // 루트노드가 아니라면, 루트노드를 찾을때까지 재귀적으로 호출 합니다. if(x == parent[x]) return x; return parent[x] = findParent(parent[x]); } //두 원소가 속한 집합을 합칩니다. public static void unionParent(int a, int b) { a = findParent(a); b = findParent(b); if( a < b) parent[b] = a; else parent[a] = b; } public static void main(String[] args) throws IOException{ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); R = Integer.parseInt(st.nextToken()); C = Integer.parseInt(st.nextToken()); arr = new char[R][C]; parent = new int[R*C]; // R*C 개의 그룹이 있습니다. visited = new boolean[R][C]; for(int i=0;i<R;i++) { String str = br.readLine(); for(int j=0;j<C;j++) { arr[i][j] = str.charAt(j); parent[i*C + j] = i*C + j; //Parent를 본인의 인덱스 넘버로 초기화시킵니다. if(arr[i][j] == 'L' && firstSwan == null) { firstSwan = new Node(i, j); arr[i][j] = '.'; // 백조의 위치 또한 물입니다. }else if(arr[i][j] =='L' && secondSwan == null){ secondSwan = new Node(i, j); arr[i][j] = '.'; // 백조의 위치 또한 물입니다. } } } Queue<Node> waterQ = new LinkedList<>(); for(int i=0;i<R;i++) { //처음 시작점에서 물의 위치를 저장하고, 각 그룹들을 나눠주기 위한 작업을 처리합니다. for(int j=0;j<C;j++) { if(arr[i][j] == 'X') continue; //빙판일경우 상관쓰지 않고 넝머갑니다. for(int dir=0;dir<4;dir++) { int nr = i + dx[dir]; int nc = j + dy[dir]; if(nr < 0 || nr >= R || nc < 0 || nc >= C) continue; if(arr[nr][nc] == '.' || arr[nr][nc] =='L') { //만약 상하좌우에 물이 있을경우 해당 그룹을 맞춰줍니다. unionParent(i * C + j, nr * C + nc); } else if(arr[nr][nc] == 'X' && visited[nr][nc] == false) { waterQ.add(new Node(nr, nc)); visited[nr][nc] = true; } } } } while( findParent(firstSwan.r * C + firstSwan.c) != findParent(secondSwan.r * C + secondSwan.c) ) { //첫번째 백조와 두번째 visited = new boolean[R][C]; Queue<Node> waterNextQ = new LinkedList<>(); //waterNextQ, 다음에 녹을 빙판의 위치입니다. while(!waterQ.isEmpty()) { Node temp = waterQ.poll(); int r = temp.r; int c = temp.c; arr[r][c] = '.'; //waterQ는 다음에 녹을 빙판의 위치이게 바로 녹여줍니다. 그래야만, 올바르게 다음 waterQ가 작동합니다. 이렇게하지않을경우 아래의 waternextQ에 올바르게 값이 들어가지 않습니다. for(int dir=0;dir<4;dir++) { int nr = r + dx[dir]; int nc = c + dy[dir]; if(nr < 0 || nr >= R || nc < 0 || nc >= C) continue; if(arr[nr][nc] == '.' || arr[nr][nc] =='L') { //만약 상하좌우에 물이 있을경우 해당 그룹을 맞춰줍니다. unionParent(r * C + c, nr * C + nc); } else if(arr[nr][nc] == 'X' && visited[nr][nc] == false) { waterNextQ.add(new Node(nr, nc)); visited[nr][nc] = true; } } } waterQ = waterNextQ; answer += 1; } System.out.println(answer); } } class Node{ int r; int c; public Node(int r, int c) { this.r = r; this.c = c; } }
시간초과 나는 코드 ( 유의해야할점은 백조의 위치도 결국 물의 위치로 판단해야하는데 해당사항은 고려하지 않은 코드입니다.)
mport java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.*; import java.util.regex.Pattern; public class Main { public static int R, C; public static String str; public static char[][] arr; public static int answer = 0; //상하좌우 public static int[] dx = {-1,1,0,0}; public static int[] dy = {0,0,-1,1}; public static char[][] newArr; public static boolean[][] visited; public static Node startL = null; public static Node endL = null; public static void main(String[] args) throws IOException{ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); R = Integer.parseInt(st.nextToken()); C = Integer.parseInt(st.nextToken()); arr = new char[R + 2][C + 2]; newArr = new char[R+2][C+2]; for(int i=1;i<=R;i++) { String str = br.readLine(); for(int j=1;j<=C;j++) { arr[i][j] = str.charAt(j-1); if(arr[i][j] == 'L' && startL == null) { startL = new Node(i, j); }else if(arr[i][j] == 'L' && startL != null) { endL = new Node(i, j); } } } simulate(); System.out.println(); } public static void simulate() { while(true) { answer += 1; for(int i=0;i<R+2;i++) { for(int j=0;j<C+2;j++) { newArr[i][j] = arr[i][j]; } } for(int i=1;i<=R;i++) { for(int j=1; j<=C;j++) { if(arr[i][j] == 'X') { for(int dir=0;dir<4;dir++) { int nx = i + dx[dir]; int ny = j + dy[dir]; if(nx <= 0 || nx > R || ny <= 0 || ny > C) continue; if(arr[nx][ny] == '.') { newArr[i][j] = '.'; } } } } } for(int i=0;i<R+2;i++) { for(int j=0;j<C+2;j++) { arr[i][j] = newArr[i][j]; } } //한번 연산한뒤 L과 L이 만날 수 있는 방법을 찾기위해서는 L의 위치를 BFs에 너어놓고서 해당 위치를 갈 수 있는지 비교하는것이다. BFS(startL, endL); } } public static void BFS(Node start, Node end) { Queue<Node> q = new LinkedList<>(); visited = new boolean[R+2][C+2]; visited[start.r][start.c] = true; q.offer(start); while(!q.isEmpty()) { Node temp = q.poll(); int r = temp.r; int c = temp.c; if( r == end.r && c == end.c) { System.out.println(answer); System.exit(0); return ; } for(int dir=0;dir<4;dir++) { int nr = r + dx[dir]; int nc = c + dy[dir]; if(nr < 1 || nr > R || nc < 1 || nc > C) continue; if(visited[nr][nc] == true) continue; if(arr[nr][nc] == '.' || arr[nr][nc] =='L') { visited[nr][nc] = true; q.offer(new Node(nr, nc)); } } } } } class Node{ int r; int c; public Node(int r, int c) { this.r = r; this.c = c; } }
'알고리즘 > Disjoint Set' 카테고리의 다른 글
[백준] 4195 친구 네트워크 - 유니온파인드(Union Find) + 해쉬맵(HashMap) JAVA (0) | 2023.09.05 |
---|---|
[백준] 16562 친구비 - 유니온파인드(Union Find) + 최소조건(Minimum) JAVA (0) | 2023.09.05 |
[백준] 1717 집합의 표현 - 유니온파인드(Union Find) JAVA (0) | 2023.09.05 |
[백준] 1043 거짓말 - 유니온파인드(Union Find) JAVA (0) | 2023.08.27 |
[백준] 1976 여행 가자 - 유니온 파인드(Union Find) JAVA (0) | 2023.08.12 |