백준(Python) 풀이
백준 1774번. 우주신과의 교감 (Python / 파이썬)
yewonnie
2022. 4. 7. 13:55
문제
황선자씨는 우주신과 교감을 할수 있는 채널러 이다. 하지만 우주신은 하나만 있는 것이 아니기때문에 황선자 씨는 매번 여럿의 우주신과 교감하느라 힘이 든다. 이러던 와중에 새로운 우주신들이 황선자씨를 이용하게 되었다.
하지만 위대한 우주신들은 바로 황선자씨와 연결될 필요가 없다. 이미 황선자씨와 혹은 이미 우주신끼리 교감할 수 있는 우주신들이 있기 때문에 새로운 우주신들은 그 우주신들을 거쳐서 황선자 씨와 교감을 할 수 있다.
우주신들과의 교감은 우주신들과 황선자씨 혹은 우주신들 끼리 이어진 정신적인 통로를 통해 이루어 진다. 하지만 우주신들과 교감하는 것은 힘든 일이기 때문에 황선자씨는 이런 통로들이 긴 것을 좋아하지 않는다. 왜냐하면 통로들이 길 수록 더 힘이 들기 때문이다.
또한 우리들은 3차원 좌표계로 나타낼 수 있는 세상에 살고 있지만 우주신들과 황선자씨는 2차원 좌표계로 나타낼 수 있는 세상에 살고 있다. 통로들의 길이는 2차원 좌표계상의 거리와 같다.
이미 황선자씨와 연결된, 혹은 우주신들과 연결된 통로들이 존재한다. 우리는 황선자 씨를 도와 아직 연결이 되지 않은 우주신들을 연결해 드려야 한다. 새로 만들어야 할 정신적인 통로의 길이들이 합이 최소가 되게 통로를 만들어 “빵상”을 외칠수 있게 도와주자.
입력
첫째 줄에 우주신들의 개수(N<=1,000) 이미 연결된 신들과의 통로의 개수(M<=1,000)가 주어진다.
두 번째 줄부터 N개의 줄에는 황선자를 포함하여 우주신들의 좌표가 (0<= X<=1,000,000), (0<=Y<=1,000,000)가 주어진다. 그 밑으로 M개의 줄에는 이미 연결된 통로가 주어진다. 번호는 위의 입력받은 좌표들의 순서라고 생각하면 된다. 좌표는 정수이다.
출력
첫째 줄에 만들어야 할 최소의 통로 길이를 출력하라. 출력은 소수점 둘째짜리까지 출력하여라.
문제 풀이
우주신과의 교감 문제에서 고려해야할 핵심 포인트를 정리해보았습니다.
-
1. 우주신들은 바로 황선자씨와 연결될 필요가 없으며 통로가 길면 안된다.
2. 통로들의 길이는 2차원 좌표계상의 거리와 같다.
3. 아직 연결이 되지 않은 우주신들을 연결해드리는데, 새로 만들 통로의 길이 합이 최소가 되게 한다.
-
우주신들은 바로 황선자씨와 연결될 필요가 없고, 통로의 길이가 최소가 되어야 합니다.
이러한 조건을 만족시키는 것은 최소비용 신장트리 입니다.
최소비용 신장트리는 바로 연결되어 있지 않지만, 노드들이 다른 노드를 거쳐 모두 연결되어있고
사이클이 발생하지 않으며, 간선의 비용이 최소인 트리입니다.
따라서, Kruskal 알고리즘을 이용하면 최소비용 신장트리를 구할 수 있습니다.
여기서 주의해야 할점은, 이미 연결된 통로들이 존재하며
새로 만들어야 할 통로의 길이는 2차원 좌표계상의 거리와 같다는 것입니다.
2차원 좌표계상의 거리는
다음과 같이 계산할 수 있습니다. 따라서 코드에서는 get_dist 함수를 만들어 거리를 계산하는
함수를 만들어주고 math 라이브러리를 이용하여 계산해주었습니다.
My Code
import math
# 루트노드 찾기 연산
def find_parent(parent, x):
if parent[x] != x:
parent[x] = find_parent(parent, parent[x])
return parent[x]
# 두 노드를 합치는 연산
def union_parent(parent, a, b):
a = find_parent(parent, a)
b = find_parent(parent, b)
if a < b:
parent[b] = a
else:
parent[a] = b
# 두 점 사이의 거리 구하기
def get_dist(a, b):
return math.sqrt(pow(a[0] - b[0], 2) + pow(a[1] - b[1], 2))
n, m = map(int,input().split()) # 우주신 개수, 이미 연결된 통로 개수
parent = [0] * (n + 1) # 루트 노드 저장할 list
for i in range(1, n + 1):
parent[i] = i # 초기 루트 노드는 자기 자신
pos = []
for _ in range(n):
x, y = map(int,input().split()) # 우주신들의 좌표
pos.append((x, y)) # 좌표값을 저장
for _ in range(m):
a, b = map(int,input().split()) # 이미 연결된 통로
union_parent(parent, a, b) # 두 통로 연결해줌
edges = []
for i in range(n - 1):
for j in range(i + 1, n):
cost = get_dist(pos[i], pos[j]) # 두 점 사이의 거리를 구해 저장
edges.append((cost, i + 1, j + 1)) # 두 점 사이의 거리, 두 점의 번호를 저장
edges.sort() # 비용이 작은 간선부터 선택하기 위해 정렬
result = 0
for edge in edges:
cost, a, b = edge # 비용, 노드a, 노드b
if find_parent(parent, a) != find_parent(parent, b): # 사이클이 발생하지 않는다면
union_parent(parent, a, b) # 두 노드를 합쳐줌
result += cost # 선택된 간선 비용 더해줌
print('%.2f'%result) # 소수점 둘째자리까지 출력