• TOC {:toc}

이 글은 백준 온라인 저지의 1939번 문제를 파이썬(Python)으로 풀이한 것을 모아놓은 글입니다.

일종의 연습 기록이며 제가 정답을 받은 코드와 참고할만한 다른 코드를 같이 기록합니다. 필요한 경우 코드에 대한 해설을 기록합니다만 코드는 통과했어도 해설은 틀릴 수 있기 때문에 가볍게 참고해주시길 부탁드립니다. 피드백은 편하신 방법으로 자유롭게 주시면 감사하겠습니다.

2021.06.01

틀린 풀이입니다. 정답은 참고 답안을 참고해주세요.

단계 시작 시각 끝난 시각 걸린 시간
문제 이해 23:46:44 23:49:54
풀이 생각 23:49:57 00:05:07
코딩 20:34:07 22:18:18
디버깅 1 22:18:48 23:18:23
디버깅 2 12:08:09 12:59:00
import sys
sys.setrecursionlimit(10 ** 8)
input = sys.stdin.readline

def find(A):
    global I, E
    tmp = [0]

    for des in I[A]:
        B, w = des
        if w:
            i = I[B].index([A, w])
            I[B][i][1] = 0
            if B == E:
                tmp.append(w)
            else:
                tmp.append(min(w, find(B)))

    return max(tmp)

N, M = map(int, input().split())

I = {}
for _ in range(M):
    a, b, c = map(int, input().split())
    if not (a in I):
        I[a] = []
    if not (b in I):
        I[b] = []
    I[a].append([b, c])
    I[b].append([a, c])

S, E = map(int, input().split())

print(find(S))

아이디어 & 풀이

특정 경로의 각 섬을 거치면서는 중량의 최솟값을, 각 경로끼리 비교할 때는 중량의 최댓값을 결괏값으로 반영할 수 있도록 한다.

  • 입력받은 섬에 연결된 모든 섬에 대해서 각 섬으로 갈 수 있는 중량 제한을 tmp 리스트에 담는다.
    • 연결된 섬이 목적지(i.e., E) 이면 해당 섬까지의 중량 제한 w
    • 목적지가 아니면 해당 섬을 다시 함수에 넣었을 때 반환한 값과 해당 섬까지의 중량 제한 w 중 최솟값을 tmp에 추가한다.
  • tmp 중 최댓값을 반환한다.

디버그

  • 처음에 연결 여부를 이차원 배열로 구현했는데 이 때문에 메모리 초과가 났다.
  • 딕셔너리 형으로 바꿨는데 런타임 에러가 났다.

피드백

통과 안 되는 케이스 찾아내고 올바르게 고쳐보기

  • [[boj-2110]]{공유기 설치} 문제와 똑같이 무게를 먼저 정하고 그 무게에서 경로로 이동할 수 있는지를 확인하면서 적절한 무게를 찾아가는 방식으로 풀어야 하는 것이었다.

참고 답안 1

import sys
from collections import deque
input = sys.stdin.readline

# 그래프를 순회하는 함수
def bfs(w):
    visited = [False] * (N + 1)
    # 출발 노드를 queue에 넣는다.
    queue = deque([S])
    # 출발 노드의 visited 상태를 True로 바꾼다.
    visited[S] = True
    
    # queue가 존재하는 동안
    while queue:
        # queue의 첫 원소를 pop 한 뒤
        a = queue.popleft()
        # 해당 섬과 연결된 섬들을 순회하면서
        for b, c in I[a]:
            # 해당 섬의 visited 상태가 False이고
            # 현재 섬과 해당 섬 사이의 제한 하중이 w보다 크거나 같으면
            if not visited[b] and c >= w:
                # 해당 섬으로 이동한다.
                # 해당 섬의 visited 상태를 True로 바꾸고
                visited[b] = True
                # queue에 추가한다.
                queue.append(b)

    # 목표 노드의 visited 상태를 반환한다.
    return visited[E]

N, M = map(int, input().split())

start = 1000000000
end = 1

I = [[] for _ in range(N + 1)]
# 섬 리스트를 구성한다.
for _ in range(M):
    a, b, c = map(int, input().split())
    I[a].append((b, c))
    I[b].append((a, c))
    # 최소 무게
    start = min(start, c)
    # 최대 무게
    end = max(end, c)

S, E = map(int, input().split())
res = start

# 이진 탐색을 이용해 무게의 중간값을 찾는다.
while (start <= end):
    mid = (start + end) // 2
    if bfs(mid):
        start = mid + 1
        res = mid
    else:
        end = mid - 1
 
print(res)

아이디어 & 풀이

이진 탐색(binary search)을 이용하는 문제이다. 응용 방식은 [[boj-2110]]{공유기 설치} 문제와 동일하다.

탐색 대상은 물품의 중량 w이고 중간값을 체크하는 과정은 다음과 같다.

  • bfs(w)는 시작 섬 S에서부터 너비우선탐색(bfs) 방식으로 방문할 수 있는 모든 섬을 방문한다.
    • 현재 섬과 방문하려는 섬 사이의 다리 중량 제한이 w보다 크거나 같으면 해당 섬을 방문할 수 있다.
    • 해당 섬을 방문할 때마다 방문 여부인 visitedTrue로 바꾼다.
  • 목적지인 섬 E의 방문 여부(i.e., visited[E])를 함수의 결괏값으로 반환한다.
  • bfs(w)의 반환 값을 확인한다.
    • True이면 물품의 중량 w가 목적지 E를 도달하기에 충분히 작은 값이기 때문에 무게를 늘리기 위해 startmid + 1로 키운다.
    • False이면 중량 w가 너무 커 목적지 E에 도달하지 못했다는 뜻이므로 무게를 줄이기 위해 endmid - 1로 줄인다.

참고 답안 2

# 풀이 2-1
import sys
input = sys.stdin.readline

class Island:
    def __init__(self, n):
        # parent: [0, 1, ..., n)]
        self.parent = list(range(n + 1))

    def union(self, x, y):
        self.parent[self.find(x)] = self.find(y)

    def find(self, x):
        # parent의 x번째의 값이 x가 아니면
        if self.parent[x] != x:
            # x번째 값을 그 값을 find 메소드로 넘긴 값으로 바꾼다.
            self.parent[x] = self.find(self.parent[x])
        # parent의 x번째 값을 반환한다.
        return self.parent[x]


N, M = map(int, input().split())
I = Island(N)

# 입력받은 값으로 튜플을 생성한 것의 리스트를 
# 중량 제한 순으로 정렬한다.
bridges = sorted([tuple(map(int, input().split())) for _ in range(M)], key=lambda x: x[2])

S, E = map(int, input().split())
ans = 0

while I.find(S) != I.find(E):
    a, b, c = bridges.pop()
    I.union(a, b)
    ans = c

print(ans)

# 풀이 2-2: heap 사용
import sys
from heapq import heappush, heappop
input = sys.stdin.readline

def find(node):
    if node == parent[node]:
        return node

    p = find(parent[node])
    parent[node] = p
    return p

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

    if pa != pb:
        parent[pb] = pa

def solve(s, e):
    while bridges:
        w, n1, n2 = heappop(bridges)
        w = abs(w)
        union(n1, n2)

        if find(s) == find(e):
            return w

n, m = map(int, input().split())
parent = [i for i in range(n + 1)]
bridges = []

for _ in range(m):
    s, e, w = map(int, input().split())
    # 최대 힙을 사용해야 하므로 -w를 넣는다.
    heappush(bridges, [-w, s, e])

s, e = map(int, input().split())
print(solve(s, e))

아이디어 & 풀이

각 섬의 연결을 관리하는 Island 클래스를 이용한다. Island 클래스는 다음과 같은 프로퍼티를 갖는다.

  • parent: 현재 인덱스 번째 섬이 최종적으로 향하는 섬의 번호를 담는 리스트이다.
    • 초기화시 모든 섬이 연결되지 않았으므로 자기 자신을 향한다.
    • 즉, 각 인덱스 번쨰 값은 그 인덱스 값이다. (e.g., parent = [0, 1, 2, ..., n])
  • findx가 최종적으로 향하는 섬의 번호를 찾아서 그 값을 반환한다.
    • ‘최종적으로 향하는 섬’은 다른 섬으로 이어지지 않는(i.e., parent 값이 자기 자신인)섬을 의미한다.
    • 마지막 섬까지 도달하는 과정에서 경로에 있는 모든 섬의 parent 값을 해당 섬으로 바꾸게 된다.
  • union 입력받은 x가 최종적으로 향하는 섬과 y가 최종적으로 향하는 섬을 연결한다.
    • x가 최종적으로 향하는 섬의 parent 값이 y가 최종적으로 향하는 섬의 번호가 된다.

이를 이용해 건널 수 있는 무게의 ‘최댓값’을 찾는 과정은 다음과 같다.

  • 입력받은 연결 정보를 중량 제한에 대해 정렬한다.
  • 출발 섬 S와 목적지 E를 입력받는다.
  • 다음의 과정을 반복하면서 최대 무게 값을 찾는다.
    • 각 연결 정보를 중량 제한이 큰 것부터 popIsland에 반영한다.
      • union으로 a, b에 따라 parent를 변경한다.
      • a가 향하는 마지막 섬과 b가 향하는 마지막 섬이 연결되고 그 경로의 섬들이 각각 마지막 섬을 가리키게 된다.
    • 현재 무게 cans로 지정한다.
  • 연결 정보(parent)를 변경할 때마다 find(S)find(E)가 같은지 확인한다.
    • 두 값이 같은 것은 S가 향하는 섬과 E가 향하는 섬이 같다는 것으로 S에서 E로 이동할 수 있다는 것을 의미한다.
    • 중량값이 큰 것부터 반영했기 때문에 위 경로의 중량 제한이 남은 경로의 중량 제한 중 최댓값이다.
    • 그러므로 더이상 연결 정보를 반영하지 않고 반복을 종료한다.

풀이 2-2는 클래스가 아닌 일반 함수로 findunion을 구현했고, 정렬된 리스트 대신 최대힙을 이용했다.

참고 답안 3

import sys
from heapq import heappop, heappush
sys.setrecursionlimit(100000000)
input = sys.stdin.readline


def dijkstra(s):
    global bridges

    weight = [0] * len(bridges)
    visited = [False] * len(bridges)
    queue = [(-1000000000000, s)]

    while True:
        while queue:
            c1, a = heappop(queue)
            if not visited[a]:
                break
        else:
            break

        weight[a] = c1
        visited[a] = True

        for b, c2 in bridges[a]:
            if not visited[b]:
                # c 중 절댓값이 최솟값인 값을 넣어야 하므로
                # c1, c2의 max를 구해서 넣는다.
                heappush(queue, (max(c1, c2), b))
    return weight


n, m = map(int, input().split())
bridges = [[] for _ in range(n + 1)]

for _ in range(m):
    a, b, c = map(int, input().split())
    # 최대 힙을 구현해야하므로 중량을 음수로 바꾼다.
    bridges[a].append((b, -c))
    bridges[b].append((a, -c))

s, e = map(int, input().split())
print(-dijkstra(s)[e])