https://www.acmicpc.net/problem/1326
코드설명
너비우선탐색(BFS)을 활용합니다.
이 문제에서 어려운점은, 어떤 알고리즘을 사용해야하는 것인가?입니다.
1. 최적 부분구조
2. DFS
3. BFS
이 3가지가 떠올랐습니다.
먼저 이 문제에서 DFS와 최적 부분구조가 안되는 이유는 점프가 뒤로도, 앞으로도 가능하다는 점입니다.
만약 점프가 앞으로만 가능했다면 최적 부분구조가 성립하므로 DFS를 활용해서 시간 안에 해결할 수 있겠지요.
하지만, 뒤로도 가능하기에 무한한 방식의 점프 방식이 나올 수 있습니다. 또 사이클도 피해야하고요.
구현하며 겪었던 생각은 아래코드와 함께 작성했습니다.
코드
정답코드1입니다.
BFS를 활용한 코드입니다.
BFS를 활용하여 한번에 여러 Queue들을 탐색하므로 중복연산을 피할 수 있고, 최적의 점프로 찾을 수 있습니다.
package Main; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Arrays; import java.util.LinkedList; import java.util.Queue; import java.util.StringTokenizer; public class Main { static int N, M, P, K, A, B, X, R; static int answer = 0; static int[] arr; static int[] cache = new int[5]; static boolean[] visited = new boolean[10001]; 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()); arr = new int[N]; st = new StringTokenizer(br.readLine()); for(int i=0;i<N; i++) { arr[i] = Integer.parseInt(st.nextToken()); } st = new StringTokenizer(br.readLine()); int a = Integer.parseInt(st.nextToken()) - 1; int b = Integer.parseInt(st.nextToken()) - 1; Arrays.fill(cache, -1); // answer = DFS(a, b, new boolean[N]); answer = BFS(a, b); if(answer >= 10001) System.out.println(-1); else System.out.println(answer); } static int BFS(int src, int target) { Queue<int[]> q = new LinkedList<>(); q.offer(new int[] {src, 0}); boolean[] visited = new boolean[N]; visited[src] = true; while(!q.isEmpty()) { int[] temp = q.poll(); int now = temp[0]; int moved = temp[1]; if(now == target) { return moved; } for(int next = now + arr[now] ; next < N; next+=arr[now]) { if(visited[next] == false) { q.offer(new int[] {next, moved + 1}); visited[next] = true; } } for(int next = now - arr[now] ; next >= 0; next -= arr[now]) { if(visited[next] == false) { q.offer(new int[] {next, moved + 1}); visited[next] = true; } } } return -1; } }
DFS 및 브루트포스를 활용합니다.
3번코드처럼 최적 부분구조가 안됨을 꺠닫고, DFS로 사이클이 발생하지 않도록 유의하며 작성했습니다.
하지만, 시간초과가 발생합니다.
이유가 무엇일까요? 이유는 visited[now] = false 로 원상복구를 해주면서 이전에 탐색햇던 부분도 다시 탐색하는 것 입니다.
이를 해결하려면, visited[now] = true로 바꾸고, visited[now] = false 로 원상복구를 하지 않고, 이전에 탐색햇던 부분을 다시 탐색하지않도록 해야합니다. 하지만, 이렇게 할경우 좀 더 빠르게 도착하는 경우, 즉 최소의 경우를 탐색하지 못합니다.
이 문제의 경우 DFS를 사용하는 것보다는 BFS를 사용해야 함을 알 수 있습니다.
package Main; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Arrays; import java.util.LinkedList; import java.util.Queue; import java.util.StringTokenizer; public class Main { static int N, M, P, K, A, B, X, R; static int answer = 0; static int[] arr; static int[] cache = new int[5]; static boolean[] visited = new boolean[10001]; 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()); arr = new int[N]; st = new StringTokenizer(br.readLine()); for(int i=0;i<N; i++) { arr[i] = Integer.parseInt(st.nextToken()); } st = new StringTokenizer(br.readLine()); int a = Integer.parseInt(st.nextToken()) - 1; int b = Integer.parseInt(st.nextToken()) - 1; Arrays.fill(cache, -1); answer = DFS(a, b, new boolean[N]); if(answer >= 10001) System.out.println(-1); else System.out.println(answer); } static int DFS(int now, int target, boolean[] visited) { if(now == target) { return 0; } visited[now] = true; int ret = 10001; for(int next = now + arr[now] ; next < N && next >= 0; next+=arr[now]) { if(visited[next] == false) ret = Math.min(ret, DFS(next, target, visited) + 1); } for(int next = now - arr[now] ; next < N && next >= 0; next-=arr[now]) { if(visited[next] == false) ret = Math.min(ret, DFS(next, target, visited) + 1); } visited[now] = false; return ret; } }
3번 코드입니다. 처음에 재귀와 메모이제이션을 활용하려한 코드입니다.
하지만, 이 문제에서는 최적부분구조가 작동하지 않습니다. 왜일까요? 이유는 "순서가 강제되지 않기 때문입니다."
점프가 앞으로도 가능하고 뒤로도 가능하기 떄문입니다. 만약 LIS 문제처럼 한 방향으로만, 즉 증가하는 부분으로만 이동한다면 최적 부분구조가 성립하지만, 이 문제에서는 최적 부분구조가 성립하지 않음을 알 수 있습니다.
package Main; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Arrays; import java.util.StringTokenizer; public class Main { static int N, M, P, K, A, B, X, R; static int answer = 0; static int[] arr; static int[] cache = new int[10001]; static boolean[] visited = new boolean[10001]; 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()); arr = new int[N]; st = new StringTokenizer(br.readLine()); for(int i=0;i<N; i++) { arr[i] = Integer.parseInt(st.nextToken()); } st = new StringTokenizer(br.readLine()); int a = Integer.parseInt(st.nextToken()) - 1; int b = Integer.parseInt(st.nextToken()) - 1; Arrays.fill(cache, -1); answer = DFS(a, b); if(answer >= 10001) System.out.println(-1); else System.out.println(answer); } static int DFS(int now, int target) { if(now == target) { return 0; } if(cache[now] != -1) return cache[now]; int ret = 10001; for(int next = now + arr[now] ; next < N && next >= 0; next+=arr[now]) { if(visited[next] == false) ret = Math.min(ret, DFS(next, target, visited) + 1); } for(int next = now - arr[now] ; next < N && next >= 0; next-=arr[now]) { if(visited[next] == false) ret = Math.min(ret, DFS(next, target, visited) + 1); } return cache[now] = ret; } }
'알고리즘 > 백준' 카테고리의 다른 글
[백준] 12933 오리 - 구현(Implementation) + 스택(Stack) JAVA (0) | 2024.09.16 |
---|---|
[백준] 2885 초콜릿 식사 - 분할정복(DivideAndConquer) JAVA (0) | 2024.09.15 |
[백준] 24480 알고리즘 수업 - 깊이 우선 탐색 2 - DFS(깊이우선탐색) JAVA (0) | 2024.09.13 |
[백준] 24479 알고리즘 수업 - 깊이 우선 탐색 1 - DFS(깊이우선탐색) JAVA (0) | 2024.09.13 |
[백준] 24445 알고리즘 수업 - 너비 우선 탐색 2 - BFS(너비우선탐색) JAVA (0) | 2024.09.12 |