문제


 

 

 

풀이


이 문제는 모든 정점은 한 번씩만 탐색되어야 한다는게 핵심이다. 첫 번째 예제를 살펴보자.

 

[그림 1] 첫 번째 예제 입력에 대한 그림

 

가장 먼저 1번 노드부터 탐색을 진행한다.

[그림 2] 첫 번째 탐색

 

1번 노드부터 탐색을 시작했더니 3번 노드가 사이클을 형성한다는걸 찾았다.

이게 이 문제의 포인트인데, 사이클 형성 여부를 찾기 위해 가장 쉽게 생각해볼 수 있는 방법은 다음과 같다.

  • 처음 탐색을 시작한 노드 == 마지막으로 탐색할 노드 

 

위의 방법으로 탐색을 진행하게 된다면 중복 탐색이 많이 발생하게 된다는 단점이 있다.

1번 노드가 사이클이 형성되지 않는단 걸 알지만, 다음 2번 노드의 탐색을 진행할 때 2 - 1 -3 으로 1번과 똑같은 탐색을 진행하게 된다.

다음 그림을 보자.

 

조건을 아래와 같이 바꾼다.

다음 노드가 탐색 시작 노드와 같은지 ---------> 다음 노드가 탐색을 통해 거쳐왔던 노드 중 하나와 같은지 

private static int dfs(int start, int current, int depth) {
// ...중략
if (check[next] != 0) {
if (start == firstParent[next]) return depth - check[next] + 1;
else return 0;
}
// ...중략
}

 

 

 

코드


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
public class Main {
private static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
private static int T;
private static int[] edge;
private static int[] check;
private static int[] firstParent;
public static void main(String[] args) throws IOException {
T = Integer.parseInt(br.readLine());
while (T-- > 0) {
int n = Integer.parseInt(br.readLine());
firstParent = new int[n + 1];
check = new int[n + 1];
edge = new int[n + 1];
StringTokenizer st = new StringTokenizer(br.readLine());
for (int j = 1; j <= n; j++)
edge[j] = Integer.parseInt(st.nextToken());
int count = 0;
for (int i = 1; i <= n; i++) {
if (check[edge[i]] == 0)
count += dfs(i, i, 1);
}
System.out.println(n - count);
}
br.close();
}
private static int dfs(int start, int current, int depth) {
check[current] = depth;
firstParent[current] = start;
int next = edge[current];
if (check[next] != 0) { // 방문했던 노드라면, 다음에 방문할 노드를 확인하여 사이클 형성 여부를 확인한다.
if (start == firstParent[next]) return depth - check[next] + 1;
else return 0;
}
return dfs(start, next, depth + 1);
}
}

댓글

댓글을 사용할 수 없습니다.