문제
때는 2040년, 이민혁은 우주에 자신만의 왕국을 만들었다. 왕국은 N개의 행성으로 이루어져 있다. 민혁이는 이 행성을 효율적으로 지배하기 위해서 행성을 연결하는 터널을 만들려고 한다.
행성은 3차원 좌표위의 한 점으로 생각하면 된다. 두 행성 A(xA, yA, zA)와 B(xB, yB, zB)를 터널로 연결할 때 드는 비용은 min(|xA-xB|, |yA-yB|, |zA-zB|)이다.
민혁이는 터널을 총 N-1개 건설해서 모든 행성이 서로 연결되게 하려고 한다. 이때, 모든 행성을 터널로 연결하는데 필요한 최소 비용을 구하는 프로그램을 작성하시오.
입력
첫째 줄에 행성의 개수 N이 주어진다. (1 ≤ N ≤ 100,000) 다음 N개 줄에는 각 행성의 x, y, z좌표가 주어진다. 좌표는 -109보다 크거나 같고, 109보다 작거나 같은 정수이다. 한 위치에 행성이 두 개 이상 있는 경우는 없다.
출력
첫째 줄에 모든 행성을 터널로 연결하는데 필요한 최소 비용을 출력한다.
예제 입력 1
5
11 -15 -15
14 -5 -15
-1 -1 -5
10 -4 -1
19 -4 19
예제 출력 1
4
나의 풀이
이 문제는 x, y, z를 각각 기준으로 하여금 정렬을 해서 각 행성별로 이웃한 행성과의 거리를 최소힙에 넣은 뒤 크루스칼 알고리즘을 사용해 MST를 구하는 문제입니다. 문제를 보면 터널을 연결할 때의 비용은 x, y, z들의 차이 중 최솟값인 것을 확인할 수 있습니다. 만약, 모든 행성들에 대하여 이 최솟값을 구하려고 한다면 불가피하게
O(N^2)
이라는 시간복잡도를 가지게 되는데요... 문제에서 입력 조건이 N이 100,000까지이기 때문에 시간초과가 발생할 것입니다. 그렇기 때문에 이 문제를 풀기 위해서는 조금 다르게 접근을 하는 방법이 필요했습니다.어차피 터널을 연결하는 비용은 x, y, z중 한 좌표의 차이값입니다. 따라서 x, y, z을 모두 따로 생각하여 계산을 해본 뒤 작은 값들부터 확인을 해볼 수 있다는 것입니다. 이러한 접근법을 가지고 더 나아가볼 수 있는데요. 바로 x, y, z를 기준으로 각각 정렬을 해본 뒤 인접한 행성간의 차이값만 비교하는 것입니다. 예를들어,
[1,7,3,4]
가 있을때 정렬을 하면[1,3,4,7]
이 됩니다. 여기서 각 인접한 원소 즉,|1 - 3|
,|3 - 4|
,|4 - 7|
만 비교해보면 각 원소들의 최솟값을 구할 수 있습니다. 왜냐하면 1을 기준으로 3이상인 4, 7의 값과의 차이는 어차피 3과 1의 차이보다는 크기때문에 비교해볼 가치가 없기때문입니다. 이렇게 해줌으로써 통로를 만드는데 드는 비용을 계산하는 과정이 정렬하는데O(NlonN)
이고, 최솟값을 구하는데O(N)
이 들게 됩니다. 앞서 생각했던O(N^2)
보다 많이 빨라진 시간이기 때문에 해당 문제를 풀 수 있게됩니다.여기서 주의해야할 점이 있습니다. 행성의 좌표들을 저장할 때 행성을 식별할 수 있는 번호(?)를 함께 저장해야한다는 것입니다. 위의 과정에서 x, y, z를 따로따로 정렬을 하기때문에 각각의 차이의 최솟값을 구할 때 행성의 고유한 좌표를 함께 저장해주어야 크루스칼 알고리즘을 적용할 때 문제가 발생하지 않기때문입니다.
이제 터널을 연결하는 비용을 담은 최소힙이 만들어졌다면, 이를 가지고 크루스칼 알고리즘을 돌리면 됩니다. MST의 특징으로 N개의 노드가 있다면 간선의 개수는 N-1개가 되는데요. 이 특징을 이용해 반복을 하면서 union이 될때마다 카운트를 증가시켜 이 카운트가 N-1이 된다면 반복을 멈추고 정답을 출력해주면 일반적으로 조금 더 빠른 효율적인 알고리즘이 될 수 있습니다.
코드
# 2887번 행성 터널
import sys, heapq
def find(x):
global parent
if parent[x] == x:
return x
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
global parent, rank
x = find(x)
y = find(y)
if x == y:
return
if rank[x] < rank[y]:
x, y = y, x
parent[y] = x
if rank[x] == rank[y]:
rank[y] += 1
def is_same(x, y):
return find(x) == find(y)
n = int(sys.stdin.readline())
planet = []
for i in range(n):
point = list(map(int, sys.stdin.readline().split()))
planet.append([i] + point) # [행성의 고유번호, x, y, z]
parent = [0] * n
rank = [1] * n
for i in range(n):
parent[i] = i
q = []
# x를 기준으로 정렬
planet.sort(key=lambda x: x[1])
for i in range(n-1):
heapq.heappush(q, [abs(planet[i][1] - planet[i+1][1]), planet[i][0], planet[i+1][0]])
# y를 기준으로 정렬
planet.sort(key=lambda x: x[2])
for i in range(n-1):
heapq.heappush(q, [abs(planet[i][2] - planet[i+1][2]), planet[i][0], planet[i+1][0]])
# z를 기준으로 정렬
planet.sort(key=lambda x: x[3])
for i in range(n-1):
heapq.heappush(q, [abs(planet[i][3] - planet[i+1][3]), planet[i][0], planet[i+1][0]])
cnt = 0
answer = 0
while q:
cost, a, b = heapq.heappop(q)
if not is_same(a, b):
answer += cost
cnt += 1
union(a, b)
if cnt == n-1:
break
print(answer)
'Programming > Algorithm' 카테고리의 다른 글
[백준14003번] 가장 긴 증가하는 부분 수열 5 / Python3 (0) | 2020.12.28 |
---|---|
[백준12738번] 가장 긴 증가하는 부분 수열 3 / Python3 (0) | 2020.12.28 |
[백준1774번] 우주신과의 교감 / Python3 (0) | 2020.12.25 |
[백준4386번] 별자리 만들기 / Python3 (0) | 2020.12.25 |
[백준1197번] 최소 스패닝 트리 / Python3 (0) | 2020.12.25 |