본문 바로가기
알고리즘 문제 풀이

[백준 13418] 학교 탐방하기

by 다빈치코딩 2024. 1. 27.

목차

    반응형

    문제 출처 : https://www.acmicpc.net/problem/13418

     

    13418번: 학교 탐방하기

    입력 데이터는 표준 입력을 사용한다. 입력은 1개의 테스트 데이터로 구성된다. 입력의 첫 번째 줄에는 건물의 개수 N(1 ≤ N ≤ 1,000)과 도로의 개수 M(1 ≤ M ≤ N(N-1)/2) 이 주어진다. 입력의 두 번

    www.acmicpc.net

    이 문제는 최소 신장 트리를 두 번 구성해야 하는 문제 입니다. 최소 신장 트리에 대해 잘 모른다면 아래 링크를 통해 최소 신장 트리에 대해 이해하시기 바랍니다.

    https://wikidocs.net/207011

     

    05. 최소 신장 트리

    MST(Minimum Spanning Tree) 라고 불리는 최소 신장 트리를 이해하기 위해서는 먼저 신장 트리(Spanning Tree)를 이해해야 합니다. # 신장 트리란…

    wikidocs.net

    먼저 오르막길을 가장 많이 갈 수 있는 방법으로 한 번 구하고, 다음으로 내리막 길을 가장 많이 갈 수 있는 방법으로 구해 두 값의 제곱의 차로 답을 구할 수 있습니다. 우리는 크루스칼 알고리즘을 통해 이 문제를 해결해 보겠습니다.

    내리막 길은 1, 오르막 길은 0으로 표시되어 있기 때문에 이 가중치를 통해 최악의 피로도, 최선의 피로도는 쉽게 구할 수 있습니다.

    문제 풀이

    그럼 문제를 직접 풀어보도록 하겠습니다.

    입력 받기

    mii = lambda : map(int, input().split())
    N, M = mii()
    
    arr = []
    for _ in range(M + 1):
        a, b, c = mii()
        arr.append((c, a, b))
    
    arr.sort()
    

    건물의 수 N과 도로의 개수 M을 입력 받습니다. 다음으로 모든 도로의 정보를 입력 받았습니다. 이 때 도로가 오르막 길인지 내리막 길인지 알 수 있는 가중치 c의 값을 첫 번째로 받아 이 값으로 정렬이 될 수 있게 하였습니다. 그리고 입력받은 도로 arr을 정렬해 주었습니다.

    유니온 파인드

    크루스칼 알고리즘은 유니온 파인드를 통해 노드들이 사이클을 가지고 있는지 없는지를 판단합니다. 최소 신장 트리는 트리로 구성되기 때문에 사이클이 있으면 안됩니다.

    def find(a):
        if a != parent[a]:        
            parent[a] = find(parent[a])
        return parent[a]
    
    def union(a, b):
        a = find(a)
        b = find(b)
    
        if a < b:
            parent[b] = a
        else:
            parent[a] = b
    

    유니온 파인드는 정형화되어 있기 때문에 따로 설명하지 않겠습니다. 잘 이해가 안된다면 아래 링크를 통해 유니언 파인드에 대해 다시 한번 학습해 보시기 바랍니다.

    https://wikidocs.net/206632

     

    02. 유니온 파인드

    # 유니온 파인드 분리 집합으로 불리는 유니온 파인드(Union Find)는 그래프 형태의 자료들의 연결 정보를 알고 싶을 때 사용합니다. ![](https://wikidoc…

    wikidocs.net

    최악의 피로도 계산하기

    max_ans = 0
    parent = list(range(N + 1))
    for c, a, b in arr:
        if find(a) == find(b):
            continue
    
        union(a, b)
        max_ans += 1 if c == 0 else 0
    
    max_ans **= 2
    

    먼저 최악의 피로도를 계산하겠습니다. 앞서 정렬된 arr은 오름차순 정렬이 되어 있습니다. 오르막 길이 0, 내리막 길이 1이기 때문에 오르막 길을 우선적으로 연결합니다. 따라서 이 값이 최악의 피로도를 계산할 수 있게 됩니다. 유니온 파인드로 연결 정보를 확인하여 피로도를 계산한 것입니다. 오르막 길의 피로도를 모두 더한 다음에 마지막에 그값을 제곱하여 값을 구합니다.

    최선의 피로도 계산하기

    min_ans = 0
    parent = list(range(N + 1))
    for c, a, b in arr[::-1]:
        if find(a) == find(b):
            continue
    
        union(a, b)
        min_ans += 1 if c == 0 else 0
    
    min_ans **= 2 
    
    print(max_ans - min_ans)
    

    다음으로 최선의 피로도를 구해보겠습니다. 유니온 파인드를 다시 해야 하기 때문에 parent를 다시 초기화 하였습니다. 그리고 정렬된 도로 arr을 거꾸로 하였습니다. arr[::-1]에 대해 잘 모르면 아래 링크를 통해 리스트를 거꾸로 하는 방법에 대해 확인해 보시기 바랍니다.

    https://wikidocs.net/192334#2-

     

    01. 리스트 기본 사용방법

    [TOC] # 리스트(list) 리스트는 말 그대로 목록이라는 뜻입니다. 우리가 음악을 들을 때 좋아하는 음악을 모아놓은 플레이 리스트가 있고, 죽기 전에 해보고 싶은 일들을 …

    wikidocs.net

    마지막으로 최악의 피로도에서 최선의 피로도를 빼서 출력하면 됩니다.

    전체 코드

    전체 코드를 확인해 보겠습니다.

    mii = lambda : map(int, input().split())
    N, M = mii()
    
    arr = []
    for _ in range(M + 1):
        a, b, c = mii()
        arr.append((c, a, b))
    
    arr.sort()
    def find(a):
        if a != parent[a]:        
            parent[a] = find(parent[a])
        return parent[a]
    
    def union(a, b):
        a = find(a)
        b = find(b)
    
        if a < b:
            parent[b] = a
        else:
            parent[a] = b
    
    max_ans = 0
    parent = list(range(N + 1))
    for c, a, b in arr:
        if find(a) == find(b):
            continue
    
        union(a, b)
        max_ans += 1 if c == 0 else 0
    
    max_ans **= 2
    
    min_ans = 0
    parent = list(range(N + 1))
    for c, a, b in arr[::-1]:
        if find(a) == find(b):
            continue
    
        union(a, b)
        min_ans += 1 if c == 0 else 0
    
    min_ans **= 2
    print(max_ans - min_ans)
    
    반응형