본문 바로가기
Algorithm/Baekjoon

1774. 우주신과의 교감 (Python)

by 원만사 2022. 6. 10.
반응형
 

1774번: 우주신과의 교감

(1,1) (3,1) (2,3) (4,3) 이렇게 우주신들과 황선자씨의 좌표가 주어졌고 1번하고 4번이 연결되어 있다. 그렇다면 1번하고 2번을 잇는 통로를 만들고 3번하고 4번을 잇는 통로를 만들면 신들과 선자씨끼

www.acmicpc.net

 

풀이

 최소 신장 트리(MST)를 이용하여 해결하는 문제이다. MST를 만드는 방법에는 크루스칼 알고리즘과 프림 알고리즘이 있다. 자세한건 검색을 통해 알아보자. 간단하게 설명하면 다음과 같다.

* 크루스칼 알고리즘 : 모든 간선을 길이에 따라 오름차순으로 정렬하여 크기가 작은 간선부터 사용한다. 간선을 사용했을 때 사이클이 발생하지 않는지를 체크한다.

* 프림 알고리즘 : 시작 정점을 기준으로 선택할 수 있는 간선 중 가장 크기가 작은 간선을 사용하여 점점 확장해 나가며 MST를 완성하는 알고리즘

 

 N은 정점의 수이고 E는 간선의 개수일 때, 크루스칼 알고리즘의 시간 복잡도는 O(E logE)이고 프림 알고리즘의 시간 복잡도는 O(N^2)이다. 이 때, 문제의 조건에 따라 알고리즘을 선택해서 사용하는 것이 좋다.

 정점에 비해 간선이 적은 그래프에서는 크루스칼 알고리즘이 적합하고, 정점에 비해 간선이 많은 그래프에서는 프림 알고리즘이 적합하다.

 

 해당 문제는 모든 좌표가 연결될 수 있는 후보에 속한다. 그렇기 때문에 간선이 많은 그래프에 속하기 때문에 프림 알고리즘을 사용하면 시간을 단축시킬 수 있다. 실제로 크루스칼 알고리즘을 사용했을 때와 프림 알고리즘을 사용했을 때 걸린 시간을 비교하면 다음과 같다.

 

[크루스칼 알고리즘]

 

[프림 알고리즘]

 

 프림 알고리즘을 사용할 때 이미 연결된 통로의 경우에는 거리를 0으로 설정해주도록 하자. 모든 좌표끼리의 거리를 구할 때 중복된 좌표끼리의 거리가 들어가지만 좌표들끼리의 거리가 0보다 작을 수 없기 때문에 거리가 0인 좌표가 선택됨이 보장된다.

 

코드

[크루스칼 알고리즘]

import sys
import heapq
import math
input = sys.stdin.readline


# 크루스칼 알고리즘을 위한 union-find
def find(n):
    if p[n] == n:
        return n
    p[n] = find(p[n])
    return p[n]


def union(a, b):
    a = find(a)
    b = find(b)

    if a == b:
        return

    p[b] = a


if __name__ == '__main__':
    N, M = map(int, input().split())
    coords = [(0, 0)] # 좌표 리스트
    p = [i for i in range(N + 1)] # 각 좌표의 부모 리스트 
    q = []

    for _ in range(N):
        a, b = map(int, input().split())
        coords.append((a, b))

    # 이미 연결된 통로는 union을 통해서 이어준다.
    for _ in range(M):
        x, y = map(int, input().split())
        union(x, y)

    # 모든 좌표에 대하여 길이를 구해준다.
    for i in range(1, N):
        for j in range(i+1, N+1):
            x = abs(coords[j][0] - coords[i][0])
            y = abs(coords[j][1] - coords[i][1])
            dist = math.sqrt(x ** 2 + y ** 2)
            heapq.heappush(q, (dist, i, j)) # 모든 간선을 최소힙에 넣어준다.

    res = 0.0
    # 이미 M개의 간선이 사용됐으므로 M이 N-1개가 될 때까지 진행한다.
    # 이미 연결된 통로는 union을 통해서 연결했기 때문에 다시 선택되는 경우는 없다.
    while M != N - 1 and len(q) != 0:
        dist, a, b = heapq.heappop(q)

        if find(a) != find(b):
            union(a, b)
            res += dist
            M += 1

    res = '{:.2f}'.format(res)
    print(res)

 

[프림 알고리즘]

import math
import sys
from collections import defaultdict
input = sys.stdin.readline
INF = float('inf')


def prim(start):
    distances[start] = 0  # 시작 정점의 거리를 0으로 설정
    res = 0.0

    # N번 반복한다.
    for _ in range(N):
        minNextNode, minDist = 0, INF

        # 현재 연결된 집합에서 아직 방문하지 않았고 거리가 가까운 좌표를 찾는다.
        for i in range(1, N+1):
            if not visited[i] and distances[i] < minDist:
                minNextNode = i
                minDist = distances[i]

        # 위에서 찾은 좌표를 방문 체크해주고 거리를 res에 더해준다.
        visited[minNextNode] = True
        res += minDist

        # 새롭게 추가된 좌표에서 연결되지 않은 좌표 중 갈 수 있는 좌표와 더 작은 거리 값을 가지고 있는 좌표를 찾아서
        # distances 배열을 갱신해준다.
        for dist, node in graph[minNextNode]:
            if not visited[node] and distances[node] > dist:
                distances[node] = dist

    return '{:.2f}'.format(res)


if __name__ == '__main__':
    N, M = map(int, input().split())
    graph = defaultdict(list)
    coords = [(0, 0)]  # 좌표 리스트
    visited = [False] * (N + 2)  # 집합에 포함된 좌표인지를 체크하기 위한 리스트
    distances = [INF for _ in range(N + 1)]  # 현재 집합에서 갈 수 있는 좌표들의 최소 거리

    for _ in range(N):
        x, y = map(int, input().split())
        coords.append((x, y))

    # 이미 연결된 통로 입력
    # 거리를 0으로 설정하여 넣어준다. 0보다 작은 거리 값은 없기 때문에 MST를 구성할 때 해당 경로가 선택됨이 보장된다.
    for _ in range(M):
        a, b = map(int, input().split())
        graph[a].append((0, b))
        graph[b].append((0, a))

    for i in range(1, N + 1):
        for j in range(i+1, N + 1):
            x = abs(coords[j][0] - coords[i][0])
            y = abs(coords[j][1] - coords[i][1])
            dist = math.sqrt(x ** 2 + y ** 2)

            graph[i].append((dist, j))
            graph[j].append((dist, i))

    # 시작 정점은 임의로 1로 설정하여 프림 알고리즘 수행
    print(prim(1))
반응형

'Algorithm > Baekjoon' 카테고리의 다른 글

4811. 알약 (Python)  (0) 2022.06.13
2624. 동전 바꿔주기 (Python)  (0) 2022.06.11
17071. 숨바꼭질 5 (Python)  (0) 2022.06.09
17136. 색종이 붙이기 (Python)  (0) 2022.06.07
2240. 자두나무 (Python)  (0) 2022.06.06

댓글