문제풀이/백준

백준 11657 타임머신 문제풀이

soo-dal 2024. 1. 15. 11:53

문제 

https://www.acmicpc.net/problem/11657

 

11657번: 타임머신

첫째 줄에 도시의 개수 N (1 ≤ N ≤ 500), 버스 노선의 개수 M (1 ≤ M ≤ 6,000)이 주어진다. 둘째 줄부터 M개의 줄에는 버스 노선의 정보 A, B, C (1 ≤ A, B ≤ N, -10,000 ≤ C ≤ 10,000)가 주어진다. 

www.acmicpc.net

 

 

문제 접근 방식

단일 노드에서 모든 노드로의 최단 경로의 길이를 구하는 문제이며, 간선간의 음의 가중치가 존재한다. 

가중치 그래프  +단일 노드 + 음의 간선이 존재 이기 때문에 벨만 포드 알고리즘을 활용하여 문제를 풀었다.

 

벨만 포드에 대한 내용을 짧게 정리를 해보자면 아래와 같다.

1. s(시작노드)로 부터 i 개의 간선 만큼 떨어져있는 노드까지의 최단 경로 길이를 저장하는 dist라는 배열을 선언한다.

dist[v] = s(시작노드)로 부터 i 개의 간선 만큼 떨어져있는 노드 v 까지의 최단 경로 길이

 

2. 간선의 길이를 하나씩 늘려가면서 V-1 개의 간선만큼 떨어져 있을 때의 dist를 구한다.

        2.1. V-1번 까지 구하는 이유 > 모든 노드 V를 사용하여 하나로 이었을 때, 간선의 수는 V-1 개 이기 때문이다.

        2.2. dist 갱신(아래 1,2번 비교하여 작은 값으로 갱신)

            1. 경유지 u 를 거쳐 v 로 가는 최단 길이 = dist [ u ] + w           (w = u > v 길이)
            2. 지금까지 구한 s > v 로의 최단 경로 길이 = dist [ v ]

dist[v]=min(dist[u]+w ,dist[v])

 

3. 음의 사이클을 확인하기 위해 한 번더 dist를 갱신해서 값이 작아진 경우를 확인한다.

 

더 자세한 설명은 코드와 주석을 보면서 이해해보자

 

 

시간 복잡도

[노드 수-1] 만큼 순회해야하기 때문에 O(V) 가 걸리고, 순회할 때 마다 모든 간선을 확인해야 하므로 각 순회마다 O(E)가 소요된다. 전체적인 소요 시간은 O(V * E) 가 되고, V<=500, E<=6000 이므로 시간 내에 동작함을 알 수 있다.

 

 

코드

코드(주석  O)

INF=int(1e9)

def bellman_ford():
    dist = [INF for _ in range(n + 1)]
    dist[1] = 0

    # 노드의 수 V -1 만큼 순회
    # s > 특정 노드 까지 사용되는 간선의 수 = i
    for i in range(1,n):

        # i개의 간선을 활용하여, u 노드를 거쳐서 v 노드로 가는 최단 경로의 길이(dist[v])를 갱신하기 위한 과정
        for u, v, w in edges:

            # 만약 s 와 u 가 이어져 있지 않다면 pass 한다.
            if dist[u] == INF:
                continue

            # 아래 1,2를 비교하여 작은 값으로 갱신한다
            # 1. s > u 로 가는 최단 경로( dist[u] ) + u>v 길이(w)
            # 2. 지금까지 구한 s > v 로의 최단 경로 길이
            dist[v] = min(dist[v], dist[u] + w)

    # 음수 사이클 판정
    # 마지막으로 한번 더 간선을 순회 > 값이 줄어들고 있다면 음수 사이클이 존재한다고 판정
    for u, v, w in edges:
        if dist[u] != INF and dist[v] > dist[u] + w:
            return -1

    return dist

if __name__=='__main__':
    global n,m,edges
    n,m=map(int,input().split())
    edges=[tuple(map(int,input().split())) for _ in range(m)]

    # Bellman-Ford를 활용하여 dist 반환
    dist=bellman_ford()


    # 주어진 조건에 맞게 출력
    answer=''
    if dist !=-1:
        for i in range(2,n+1):
            answer+= str(dist[i]) if dist[i] !=INF else str(-1)
            answer+='\n'
    else:
        answer='-1'

    print(answer)

 

 

코드(주석 X)

INF=int(1e9)

def bellman_ford():
    dist = [INF for _ in range(n + 1)]
    dist[1] = 0

    for i in range(1,n):
        for u, v, w in edges:
            if dist[u] == INF:
            	continue
            dist[v] = min(dist[v], dist[u] + w)

    # 음수 사이클 판정
    for u, v, w in edges:
        if dist[u] != INF and dist[v] > dist[u] + w:
            return -1

    return dist

if __name__=='__main__':
    global n,m,edges
    n,m=map(int,input().split())
    edges=[tuple(map(int,input().split())) for _ in range(m)]

    # Bellman-Ford를 활용하여 dist 반환
    dist=bellman_ford()


    # 주어진 조건에 맞게 출력
    answer=''
    if dist !=-1:
        for i in range(2,n+1):
            answer+= str(dist[i]) if dist[i] !=INF else str(-1)
            answer+='\n'
    else:
        answer='-1'

    print(answer)

 

회고

 

** 노드 s 로부터 간선 i 개 거리의 노드 v에 대해 어떻게 순회를 하여 처리하는지 배울 수 있었다.

1. 간선의 수 만큼 반복

2. 모든 간선을 순회하며, 이어져 있지 않은 경우는 pass

 

**음의 사이클을 어떻게 판정하는지 배울 수 있었다.

모든 노드를 최대로 이었을 경우 최대 간선 수(V-1) 에서 한 번더 사이클을 돌려서 값을 비교

** 노드 s 로부터 간선 i 개 거리의 노드 v에 대해 어떻게 순회를 하여 처리하는지 배울 수 있었다.
1. 간선의 수 만큼 반복
2. 모든 간선을 순회하며, 이어져 있지 않은 경우는 pass



** 음의 사이클을 어떻게 판정하는지 배울 수 있었다.
모든 노드를 최대로 이었을 경우 최대 간선 수(V-1) 에서 한 번더 사이클을 돌려서 값을 비교