Problem Solving/Baekjoon

[백준/JAVA] 20926 - 얼음미로

jihyeon99 2024. 7. 30. 17:30

문제 정보

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

 

문제

문제 해결과정

문제 분석 및 시간복잡도 계산

1. 출발점 ~ 도착점까지의 최단시간을 구하는 '최단경로' 문제였다.

2. 정점 사이의 (인접한 두 좌표간의) 가중치(미끌시간)이 달랐다.BFS를 사용할 수 없다. (BFS는 가중치가 동일해야 함)

3. 각 가중치는 0이상 9이하라고 주어졌으므로, 가중치가 양수였다. ⇒ '다익스트라'와 '플로이드워셜' 모두 사용 가능

  • 다익스트라 : '시작점 ~ 다른 모든 정점' 사이의 최단거리 구함. 시간복잡도는 O(E*logV)
  • 플로이드워셜 : '모든 정점 쌍' 사이의 최단거리 구함. 시간복잡도는 O(V^3)
  • 시간복잡도는 이전 블로그 포스팅을 참고

4. 출발점과 도착점이 주어졌으므로, 시간복잡도가 더 유리한 '다익스트라' 방식을 채택하기로 하였다.

  • 다익스트라의 경우 O(E*logV) = O(4*500*500*log(500)) = O((10^6) * log(500))
  • 플로이드워셜의 경우 O(V^3) = O(500^3) = O((10^6) * 125)
  • 사실상 인접한 네 방향으로 간선은 있지만, 결국 우선순위큐에 추가/삭제되는 것은 인접한 방향으로 미끄러지다가 바위에 부딪혔을때의 정점이다. 따라서, 실제 시간복잡도에 영향을 주는 (이웃한) 간선의 개수(E)는 더 작을 것으로 예상된다. 

 

구현 과정

1. 변수 정의

  • 방문배열(visited) : 해당 지점을 방문했는지 여부 저장
    • 방문했다 == 시작점 ~ 해당 노드까지의 최단 거리 결정됐다
  • 거리배열(dist) : 시작점부터 해당 지점까지의 최단거리를 저장
  • 최소힙(q) : 현재 바위에 부딪혀서 멈춘 지점의 정보 저장 (Node : x좌표, y좌표, 소요시간)

2. 초기화

  • 거리 배열(dist)를 아주 큰 값(INF)로 초기화하고, 시작점 값만 0으로 설정합니다.
  • 출발지를 최소힙(우선순위 큐)에 넣습니다. 

3. 반복

  • 힙에서 최솟값(루트)를 고릅니다. 이때 힙에서 해당 노드는 힙에서 삭제됩니다.
    • 뽑힌 노드가 출구(Exit)이면 종료 
    • 이미 방문한 지점이라면, 다시 반복의 시작으로 돌아갑니다.
    • 방문하지 않은 지점이라면, 방문처리를 합니다. (시작점 ~ 해당 노드까지의 최단거리는 정해졌다)
  • 뽑힌 노드에 인접한(상하좌우) 노드들을 보며, 해당 방향으로 쭉~ 미끄러집니다.
    • 절벽에 떨어지거나 / 구멍에 빠진 경우에는, 그 다음 인접한 노드를 봅니다.
    • 바위에 부딪힌 경우, 'dist[뽑힌 노드] + 가중치합(바위 앞까지 미끌시간합)'과 'dist[바위 앞 노드]'를 비교하여 'dist[바위 앞 노드]'를 더 작은 값으로 갱신해줍니다. 이때, 갱신되었다면 다시 힙에 해당 노드를 추가합니다.
    • 출구인 경우, 'dist[뽑힌 노드] + 가중치합(출구 앞까지 미끌시간합)'과 'dist[출구]'를 비교하여 'dist[출구]'를 더 작은 값으로 갱신해줍니다. 이때, 갱신되었다면 다시 힙에 해당 노드를 추가합니다.

4. 종료

  • 힙이 비었거나 출구(Exit)를 만났다면 반복이 종료되고, 출발지에서 도착지까지의 최단 거리가 결정됩니다. (도착지에 도착할 수 없는 경우 -1)

 

코드

class Node implements Comparable<Node>{
	int x;
	int y;
	int time;
	
	public Node(int x, int y, int time) {
		this.x = x; this.y = y; this.time = time;
	}
	
	@Override
	public int compareTo(Node o) {
		return this.time - o.time;
	}
}

class Main {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		// 맵의 크기 입력받기
		StringTokenizer st = new StringTokenizer(br.readLine());
		int m = Integer.parseInt(st.nextToken());
		int n = Integer.parseInt(st.nextToken());
		
		// 맵 생성
		int startx = -1, starty = -1, finishx = -1, finishy = -1;
		char[][] map = new char[n][m];
		for(int r=0; r<n; r++) {
			String s = br.readLine();
			for(int c=0; c<m; c++) {
				map[r][c] = s.charAt(c);
				
				// 시작점, 도착점 기록
				if(map[r][c] == 'T') {
					startx = r; 
					starty = c;
				} else if(map[r][c] == 'E') {
					finishx = r;
					finishy = c;
				}
			}
		}
		
		/* 초기화 */
		// 방문배열 : 해당 지점 방문 여부 저장
		boolean[][] visited = new boolean[n][m];
		
		// 거리배열 : 시작점 ~ 해당지점까지의 최단시간 저장
		int[][] dist = new int[n][m];
		for(int i=0; i<n; i++) {
			// 모든 거리를 MAX값으로 초기화
			Arrays.fill(dist[i], Integer.MAX_VALUE);
		}
		dist[startx][starty] = 0; // 시작점만 0으로
			
		// 최소힙 : 현재 바위에 부딪혁서 멈춘 지점의 정보(좌표 + 시간) 저장
		PriorityQueue<Node> q = new PriorityQueue<>();
		// 시작점 넣음
		q.offer(new Node(startx, starty, 0));
        
		
		/* 반복 */
		int minTime = -1; // 최단 탈출 시간 (도착하지 못한 경우 : -1)
		while(!q.isEmpty()) {	
			// 최소힙에서 하나 꺼냄(시간 최솟값)
			Node cur = q.poll();
			
			// 도착지점이라면 => break
			if(cur.x == finishx && cur.y == finishy) {
				minTime = dist[finishx][finishy];
				break;
			}
			
			// 이미 방문한지점이라면 => pass
			if(visited[cur.x][cur.y]) continue;
            
			visited[cur.x][cur.y] = true; // 지점 방문 했다 == 최단시간 정해졌다
			
			// 이 정점과 이웃한(상하좌우 인접) 방향으로 미끄러짐
			for(int i=0; i<4; i++) {
				Node result = slide(i, cur.x, cur.y, cur.time, map, n, m); // 그 다음 멈추는 좌표 + 이동시간
				
				// 1) 절벽에 떨어지거나 / 구멍에 빠진 경우
				if(result == null) continue;
				
				// 2) 바위에 부딪히거나 / 출구 만난 경우
				// 큐에서 꺼냈을 때의 지점(해당 지점은 포함 x) ~ 바위 앞/출구까지의 미끌시간의 합 < 현재 바위 앞/출구의 시간배열의 시간 
				if(!visited[result.x][result.y] && result.time < dist[result.x][result.y]) {
					dist[result.x][result.y] = result.time;
					q.offer(result);
				}
			}
		}
        
		
		/* 출력 */
		System.out.println(minTime);
	}
	
	// 방향벡터(상하좌우)
	static int[] dx = {-1,1,0,0};
	static int[] dy = {0,0,-1,1};
	
	static Node slide(int dir, int x, int y, int time, char[][] map, int n, int m) {
		int len = 1;
		int sum = 0;
		while(true) {
			int nextx = x + len*dx[dir];
			int nexty = y + len*dy[dir];
			
			// 1) 중간에 절벽 / 구멍 만나는 경우 => 종료
			if(nextx<0 || nextx>=n || nexty<0 || nexty>=m || map[nextx][nexty] == 'H') {
				return null;
			}

			// 2) 중간에 바위 만나는 경우
			if(map[nextx][nexty] == 'R') {				
				// 바위 바로 앞 좌표 + 걸린 시간 반환
				nextx = x + (len-1)*dx[dir];
				nexty = y + (len-1)*dy[dir];				
				return new Node(nextx, nexty, time + sum);
			}
			// 3) 출구 만나는 경우
			else if(map[nextx][nexty] == 'E') {				
				// 출구 좌표 + 걸린 시간 반환			
				return new Node(nextx, nexty, time + sum);
			}
			
			sum += map[nextx][nexty] - '0';
			len++;
		}
	}
}

 

 

'Problem Solving > Baekjoon' 카테고리의 다른 글

[백준/JAVA] 14501 - 퇴사  (0) 2024.07.19
[백준/JAVA] 2579 - 계단 오르기  (0) 2024.07.19
[SWEA/JAVA] 8275 - 햄스터  (0) 2024.07.11