# [BOJ] 9466 텀 프로젝트 - 파이썬
# 문제 요약
사이클에 속해있지 않은 노드(학생)의 개수를 구해라.
# 첫번째 접근
dfs 탐색 시잠점과 탐색 종료점이 같다면 사이클이라는 정보를 이용할수있겠다 생각했습니다. dfs 를 돌면서 이미 경로에 있는 노드를 방문하려 한다면, 종료합니다.
예를들어 (1 -> 2 -> 3 -> 1)인 그래프가 있다고 할때 dfs(1)을 실행시키면 재귀적으로 dfs(3) 까지 실행되고있는 시점에 [1, 2, 3]이 경로에 저장되있을거고, 이때 다음 노드인 1이 이미 경로에 있기때문에 경로에 1을 추가하고 종료시킵니다. 그럼 dfs(1)을 호출한 측에서는 [1,2,3,1]이라는 경로를 받게됩니다. 시작점과 끝점이 같기때문에 사이클임을 확인할수있습니다.
이런식이면 사이클임을 알수있으니 모든 노드에 대하여 이 방법을 반복하면 중복해서 요소에 접근하게되고, 시간초과가 발생합니다.
즉 위의 방법은 최적화가 필요했습니다. 단순히 시작점과 종료점만을 체크하는 방식으로는, 경로안에 사이클이 있는 경우를 찾지는 못했습니다. 만약 [1,2,3,4,2] 가 경로라면 시작점과 도착점이 1과 2로 다르기때문에 사이클이 아니라고 판단하게됩니다.
# 개선한 방법(AC)
경로가 [1,2,3,4,2] 와 같이 나왔을때 경로의 마지막 요소와 같은 요소를 찾으면 됩니다. 그러면 사이클을 찾을수있으며, 각 요소를 한번씩만 방문해도 문제를 풀수있습니다.
import sys
sys.setrecursionlimit(1000030)
input = sys.stdin.readline
def main():
tc = int(input())
for _ in range(tc):
input()
students = [int(_) - 1 for _ in input().split()]
print(solve(students))
def solve(students):
n_student = len(students)
visited = [False for _ in range(n_student)]
cnt = 0
for node in range(n_student):
if not visited[node]:
path = [node]
dfs(students, visited, path, node)
# ex, path [4, 7, 6, 4]
# ex2, path [1, 3, 3]
start = next((i for i, x in enumerate(path)
if x == path[-1]
and i != len(path) - 1), None)
if start is not None:
cnt += len(path) - start - 1
return n_student - cnt
def dfs(students, visited, path, node):
visited[node] = True
next_node = students[node]
path.append(next_node)
if not visited[next_node]:
dfs(students, visited, path, next_node)
main()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 위상정렬 접근(AC)
indegree 가 0 이라면 사이클에 속할수가 없습니다. 그렇기에 indegree 가 0 인 노드들을 찾아서, cnt(사이클에 속하지 않은 노드 개수)를 늘리고, 노드를 그래프에서 제거합니다. 노드를 그래프에서 제거한다는 것은 해당 노드가 가리키는 노드의 indegree 를 감소 시키는 것입니다.
이후 indegree가 0인 노드가 또 생기게되고 위의 과정을 반복해나가면 답을 구할수있습니다.
import sys
sys.setrecursionlimit(1000030)
input = sys.stdin.readline
def main():
tc = int(input())
for _ in range(tc):
input()
students = [int(_) - 1 for _ in input().split()]
indegree = [0 for _ in range(len(students))]
visited = [False for _ in range(len(students))]
for st in students:
indegree[st] += 1
cnt = 0
for i in range(len(students)):
cur = i
while indegree[cur] == 0 and visited[cur] == 0:
visited[cur] = True
indegree[students[cur]] -= 1
cur = students[cur]
cnt += 1
print(cnt)
main()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27