여러 원소들 중 일부 원소들의 순서만이 부분적으로 주어졌을 때, 이 순서들을 위배하지 않으면서 전체 순서를 알맞게 결정하는 문제이다.
예를 들어, 순서 1 → 2 → 3
과 4 → 2 → 5
가 주어졌을 때, 전체 원소는 1 → 4 → 2 → 3 → 5
와 같은 방식으로 정렬할 수 있다. (서로 명확한 순서 관계가 주어지지 않은 원소들끼리는 어떻게 배치되어도 괜찮다)
다행히 얼마 전 이와 비슷한 유형의 문제를 위상 정렬을 통해 해결할 수 있다는 것을 배웠다.
위상 정렬은 비순환 방향 그래프(Directed Acyclic Graph, DAG)의 정점들을 각 간선들의 방향을 거스르지 않도록 나열하는 것을 의미한다.
위와 같은 그래프가 있을 때, 11 → 5
또는 9 → 8
처럼 간선의 방향을 거스르는 부분이 없도록 전체 순서를 결정하는 방법은 이렇다.
v
를 선택하고 출력한다.v
와 연결된 진출 간선을 제거한다. 모든 v → w
에 대해 w
의 상응하는 진입 간선도 제거한다.이 방법은 이번 문제에 정확히 적용할 수 있다.
단, 코드에서는 시간 복잡도를 줄이기 위해 매번 모든 정점의 모든 진출 정점들의 개수를 세는 대신, 진출 차수를 정점별로 in_counter
배열에 저장하여 사용하는데, 이때 중복된 경로가 주어질 경우 카운트하지 않는 것에 유의하자.
import sys
input = sys.stdin.readline
N, M = map(int, input().split())
in_node = [[False] * (N + 1) for _ in range(N + 1)] # 노드별 진입 노드
out_node = [[False] * (N + 1) for _ in range(N + 1)] # 노드별 진출 노드
in_counter = [0] * (N + 1) # 노드별 진입 차수
res = []
for _ in range(M):
arr = list(map(int, input().split()))[1:]
for i in range(1, len(arr)): # arr[i - 1] -> arr[i]
if not in_node[arr[i]][arr[i - 1]]:
in_counter[arr[i]] += 1 # 중복된 경로가 아닐 때에만 카운트
in_node[arr[i]][arr[i - 1]] = True
out_node[arr[i - 1]][arr[i]] = True
for i in range(N): # 총 N번 반복
v = None
for j in range(1, N + 1):
if in_counter[j] == 0: # 진출 차수가 0인(= 최상단의) 노드를 선택
v = j
in_counter[j] -= 1 # 중복 방문하지 않도록 -1로 설정
break
if not v: # 사이클이 있는 경우 진출 차수가 0인 노드가 존재하지 않게 됨
break
res.append(v)
for k in range(1, N + 1): # 해당 노드와 연결된 모든 진출 간선을 제거
if out_node[v][k]:
out_node[v][k] = False
in_node[k][v] = False # 상대 노드의 상응하는 진입 간선도 제거
in_counter[k] -= 1
if len(res) < N:
print(0)
else:
for r in res:
print(r)