아래는 백준 25953번 템포럴 그래프 문제를 해결하는 예제입니다.
먼저 간단한 입력 예제와 함께, 파이썬 코드(전체 포함) 그리고 각 줄마다 역할, 위치 이유, 실행 흐름, 예제 결과 변화까지 꼼꼼히 설명했습니다.
5 3 6 0 4
0 1 2
1 2 3
2 3 4
3 4 5
0 2 10
1 4 100
import sys
def main():
input_data = sys.stdin.read().split()
# 줄 1: input_data = [ '5','3','6','0','4', ... ]
N, T, M, S, E = map(int, input_data[:5])
# N=5, T=3, M=6, S=0, E=4
edges = list(map(int, input_data[5:]))
# edges = [0,1,2,1,2,3, ...] 6*3*3 values but we read sequential blocks
# dp[t][v]: 시간 t에 정점 v 도달 가능한 최단 거리
INF = 10**18
dp = [[INF]*N for _ in range(T+1)]
# t=0 시점엔 출발점 S에서 거리 0, 나머진 닿을 수 없음
dp[0][S] = 0
# t단계마다 dp[t+1] 갱신
idx = 0 # edges에서 읽기 위한 인덱스
for t in range(T):
# ① 시간 t+1 기본값 = t 시점 그대로 유지
for v in range(N):
dp[t+1][v] = dp[t][v]
# 왜? 걷지 않고 가만히 있는 경우도 valid
# 현재 예: dp[1] = dp[0] -> dp[1][0]=0, 나머지 INF
# ② 간선 M개 모두 탐색
for _ in range(M):
x = edges[idx]; y = edges[idx+1]; w = edges[idx+2]
idx += 3
# 예: 첫 간선 x=0,y=1,w=2
# t 시점에 x에 갈 수 있었다면 y로 이동 고려
if dp[t][x] + w < dp[t+1][y]:
dp[t+1][y] = dp[t][x] + w
# 마찬가지로 y->x
if dp[t][y] + w < dp[t+1][x]:
dp[t+1][x] = dp[t][y] + w
# 이 시점: dp[1][1]=2, dp[1][0]=0
# T 단계 후 도착 E까지 최소거리
ans = dp[T][E]
print(ans if ans < INF else -1)
if __name__ == "__main__":
main()
import syssys.stdin.read()로 한 번에 입력을 읽기 위함input_data = sys.stdin.read().split()input_data[0]='5', ..., input_data[5]='0', input_data[6]='1', ...N, T, M, S, E = map(...)N=5, T=3, M=6, S=0, E=4edges = ...[0,1,2,1,2,3,...] 형태edges[0]=0, edges[1]=1, edges[2]=2, ...INF = 10**18dp = [[INF]*N for _ in range(T+1)]dp는 4×5 크기, 모두 INF로 초기화dp[0][S] = 0dp[0] = [0, INF, INF, INF, INF]idx = 0edges 리스트에서 읽을 위치 추적for t in range(T): ...for v in range(N): dp[t+1][v] = dp[t][v][0, INF, INF, INF, INF]for _ in range(M): ...역할: 각 단계에서 모든 간선으로 이동 시도
위치 이유: 't-1에서 t로의 이동'을 반영
흐름:
예: t=0, 첫 간선 (0→1, w=2)
다음 간선 처리 반복 → 최종적으로 t=1에서 가능한 정점: 0,1,2 등
역할: 도착지까지 최단거리 출력, 불가능하면 -1
예제: 3단계 후 E=4까지 도달 가능?
dp[0] = [0, INF, INF, INF, INF]dp[1] = [0,2,5,9,14]dp[2] = [0,2,5,9,14] (간선 동일해 추가 단축 없음)dp[3] = [0,2,5,9,14]dp[3][4] = 14 → 출력 14DP 전개: 시간 축을 따라 상태 전이
시간 복잡도: O(T × M + T × N) — 여기서 M ≥ N일 때 약 O(T×M)
중요 포인트:
dp[t+1][v] = dp[t][v]: 가만히 있는 선택 허용물론이에요! 아래 코드를 한 줄 한 줄 정말 디버깅하듯 자세히 설명해 드릴게요.
이번에도 예제 입력값을 기억하면서 값의 변화를 단계별로 추적해봅시다.
input_data = sys.stdin.read().split()
N, T, M, S, E = map(int, input_data[:5])
edges = list(map(int, input_data[5:]))
input_data = sys.stdin.read().split()sys.stdin.read():
.split():
프로그램 시작 → read() 호출 → 입력 내용을 한꺼번에 읽고 → 공백 기준 리스트화
예를 들어, 입력이:
5 3 6 0 4
0 1 2
1 2 3
2 3 4
3 4 5
0 2 10
1 4 100
일 경우:
read() → 5 3 6 0 4\n0 1 2\n1 2 3 ...split() → ['5','3','6','0','4','0','1','2','1','2','3', ...]input_data = [
'5', '3', '6', '0', '4',
'0', '1', '2',
'1', '2', '3',
'2', '3', '4',
'3', '4', '5',
'0', '2', '10',
'1', '4', '100'
]
N, T, M, S, E = map(int, input_data[:5])input_data[:5]:
input_data의 0번째~4번째 요소를 슬라이싱 → ['5','3','6','0','4']map(int, ...):
int(정수)로 변환대입 N, T, M, S, E = ...:
map(int, ['5','3','6','0','4']) → [5,3,6,0,4] → 대입:
N=5T=3M=6S=0E=4이 한 줄로 맨 처음 5개의 정보를 정리해서 프로그램의 주요 파라미터를 준비합니다.
edges = list(map(int, input_data[5:]))input_data[5:]:
input_data의 6번째 요소부터 끝까지 슬라이싱 → ['0','1','2','1','2','3', ...] (간선들)map(int, ...):
list(...):
이 리스트(edges)는 나중에 idx += 3하면서 x, y, w 형태로 읽어갑니다.
예제의 경우:
input_data[5:] = ['0','1','2','1','2','3','2','3','4','3','4','5','0','2','10','1','4','100']
map(int, ...) → list(...):
edges = [0,1,2,1,2,3,2,3,4,3,4,5,0,2,10,1,4,100]
나중에
idx = 0
x = edges[idx]; y = edges[idx+1]; w = edges[idx+2]; idx+=3
이렇게 3개씩 끊어서 읽습니다.
| 부분 | 값 (예제) | 상태 |
|---|---|---|
input_data | ['5','3','6','0','4','0','1','2','1','2','3', ...] | 리스트(문자열) |
N,T,M,S,E | 5,3,6,0,4 | 정수 |
edges | [0,1,2,1,2,3,2,3,4,3,4,5,0,2,10,1,4,100] | 정수 리스트 |
sys.stdin.read().split():
map(int, input_data[:5]):
edges = list(map(int, input_data[5:])):
이렇게 준비된 상태에서, 이후 edges를 3개씩 잘라서 x, y, w를 읽으면 됩니다.
혹시 이 부분을 디버깅하면서 출력해보고 싶다면 아래처럼 중간중간 print()를 추가해보시면 좋아요:
input_data = sys.stdin.read().split()
print("DEBUG input_data:", input_data) # 전체 출력
N, T, M, S, E = map(int, input_data[:5])
print("DEBUG N,T,M,S,E:", N,T,M,S,E) # 파라미터 출력
edges = list(map(int, input_data[5:]))
print("DEBUG edges:", edges) # 간선 리스트 출력
좋아요! 이번에도 같은 방식으로 한 줄 한 줄 디버깅하듯 꼼꼼히 설명해 드릴게요.
이 부분은 최단 경로 계산을 위한 DP(동적계획법) 테이블을 준비하는 부분이에요.
INF = 10**18
dp = [[INF]*N for _ in range(T+1)]
INF = 10**1810**18:
1,000,000,000,000,000,000 (1조의 1000배 = 10억억)이렇게 아주 큰 수를 써서 "무한대" 대신 사용.
최단 거리 계산에서 초깃값을 "무한대"라고 두어, 아직 도달하지 못한 정점을 나타냄.
min() 계산에서 무조건 갱신되도록 함.dp 초기화하기 전에 반드시 정의.dp[t][v]가 아직 도달하지 못한 정점은 이 값(10^18)로 남음.dp = [[INF]*N for _ in range(T+1)]dp: 2차원 리스트 (행렬) 생성.
크기 = (T+1)행 × N열
초기값을 전부 INF로 채우기.
for _ in range(T+1) → 바깥쪽 list comprehension[*INF]*N → N개짜리 INF 리스트 생성예제에서:
dp는 4행 5열의 리스트 생성# N=5, T=3 일 때
INF = 10**18
dp = [[INF]*N for _ in range(T+1)]
# dp 상태 출력 디버그
print("DEBUG dp 초기 상태:")
for t in range(T+1):
print(f"t={t}: {dp[t]}")
DEBUG dp 초기 상태:
t=0: [1000000000000000000, 1000000000000000000, 1000000000000000000, 1000000000000000000, 1000000000000000000]
t=1: [1000000000000000000, 1000000000000000000, 1000000000000000000, 1000000000000000000, 1000000000000000000]
t=2: [1000000000000000000, 1000000000000000000, 1000000000000000000, 1000000000000000000, 1000000000000000000]
t=3: [1000000000000000000, 1000000000000000000, 1000000000000000000, 1000000000000000000, 1000000000000000000]
이 상태에서 출발 정점만 dp[0][S]=0 으로 바꾼 후 갱신을 시작하게 됩니다.
INF = 10**18: 도달 불가 상태를 나타내는 매우 큰 수.dp = [[INF]*N for _ in range(T+1)]: T+1개 행(시간 단계) × N개 열(정점 수)로 된 2차원 리스트 생성.dp[t+1][v] = min(dp[t+1][v], dp[t][x]+w) 형태의 갱신을 받으며 "최단 거리" 계산에 사용됨.좋아요! 이제 이 부분을 아주 디버깅 관점에서 자세히 한 줄 한 줄 뜯어서 설명해 드릴게요.
idx = 0
for t in range(T):
for v in range(N):
dp[t+1][v] = dp[t][v]
idx = 0idx는 edges 리스트를 한 줄 한 줄(정확히 3개씩) 읽을 때 사용하는 포인터.idx = 0 → edges 리스트의 첫 번째 값부터 읽을 준비.edges 리스트는 나중에 t 루프 안에서 단계마다 M개의 간선을 3개 묶음(x,y,w)으로 읽어올 거라 시작 전에 초기화.edges = [0,1,2,1,2,3,2,3,4,...]idx = 0 → edges[0], edges[1], edges[2]를 가장 먼저 읽음.for t in range(T):t = 0, 1, ..., T-1 반복t 단계에서 → dp[t+1]를 계산.t=0,1,2를 순서대로 진행for v in range(N): dp[t+1][v] = dp[t][v]v = 0,1,..., N-1 정점에 대해dp[t+1]를 dp[t] 값으로 채움.예제에서 N=5일 경우:
t=0일 때
dp[1][0] = dp[0][0] = 0dp[1][1] = dp[0][1] = INFdp[1][2] = dp[0][2] = INFidx=0 준비 → edges를 맨 처음부터 읽을 준비
t=0 단계 시작
t=1 단계 시작
t=2 단계 시작
t=2 끝나면 모든 단계 종료
중간 상태를 찍고 싶으면:
idx = 0
for t in range(T):
for v in range(N):
dp[t+1][v] = dp[t][v]
print(f"DEBUG after copying dp[{t}] to dp[{t+1}]:")
print(dp[t+1]) # dp[t+1] 상태 출력
예제(T=3,N=5) 기준:
DEBUG after copying dp[0] to dp[1]:
[0, INF, INF, INF, INF]
DEBUG after copying dp[1] to dp[2]:
[0, INF, INF, INF, INF]
DEBUG after copying dp[2] to dp[3]:
[0, INF, INF, INF, INF]
이런 디버깅 출력은 "간선을 완화하기 전 상태"를 보여주므로 도움이 됩니다.
좋아요! 아주 자세하게, 주어진 조건과 입력값으로 이 부분이 어떻게 dp 배열을 바꾸는지 한 단계씩, 하나도 빠짐없이 차근차근 디버깅하겠습니다.
| t | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 | 0 | INF | INF | INF | INF |
| 1 | INF | INF | INF | INF | INF |
| 2 | INF | INF | INF | INF | INF |
| 3 | INF | INF | INF | INF | INF |
각 단계에서
| t | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 1 | 0 | INF | INF | INF | INF |
| 간선 번호 | x | y | w |
|---|---|---|---|
| 1 | 0 | 1 | 2 |
| 2 | 1 | 2 | 3 |
| 3 | 2 | 3 | 4 |
| 4 | 3 | 4 | 5 |
| 5 | 0 | 2 | 10 |
| 6 | 1 | 4 | 100 |
조건1: dp[0][0] + 2 < dp[1][1] ?
0 + 2 = 2 < INF → True
→ dp[1][1] = 2
조건2: dp[0][1] + 2 < dp[1][0] ?
INF + 2 = INF < 0 → False
→ 변화 없음
조건1: dp[0][1] + 3 < dp[1][2]?
INF + 3 = INF < INF → False
조건2: dp[0][2] + 3 < dp[1][1]?
INF + 3 = INF < 2 → False
조건1: dp[0][2] + 4 < dp[1][3]?
INF + 4 = INF < INF → False
조건2: dp[0][3] + 4 < dp[1][2]?
INF + 4 = INF < INF → False
조건1: dp[0][3] + 5 < dp[1][4]?
INF + 5 = INF < INF → False
조건2: dp[0][4] + 5 < dp[1][3]?
INF + 5 = INF < INF → False
조건1: dp[0][0] + 10 < dp[1][2]?
0 + 10 = 10 < INF → True
→ dp[1][2] = 10
조건2: dp[0][2] + 10 < dp[1][0]?
INF + 10 = INF < 0 → False
조건1: dp[0][1] + 100 < dp[1][4]?
INF + 100 = INF < INF → False
조건2: dp[0][4] + 100 < dp[1][1]?
INF + 100 = INF < 2 → False
| t | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 1 | 0 | 2 | 10 | INF | INF |
| t | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 2 | 0 | 2 | 10 | INF | INF |
조건1: dp[1][0] + 2 < dp[2][1]?
0 + 2 = 2 < 2 → False
조건2: dp[1][1] + 2 < dp[2][0]?
2 + 2 = 4 < 0 → False
조건1: dp[1][1] + 3 < dp[2][2]?
2 + 3 = 5 < 10 → True
→ dp[2][2] = 5
조건2: dp[1][2] + 3 < dp[2][1]?
10 + 3 = 13 < 2 → False
조건1: dp[1][2] + 4 < dp[2][3]?
10 + 4 = 14 < INF → True
→ dp[2][3] = 14
조건2: dp[1][3] + 4 < dp[2][2]?
INF + 4 = INF < 5 → False
조건1: dp[1][3] + 5 < dp[2][4]?
INF + 5 = INF < INF → False
조건2: dp[1][4] + 5 < dp[2][3]?
INF + 5 = INF < 14 → False
조건1: dp[1][0] + 10 < dp[2][2]?
0 + 10 = 10 < 5 → False
조건2: dp[1][2] + 10 < dp[2][0]?
10 + 10 = 20 < 0 → False
조건1: dp[1][1] + 100 < dp[2][4]?
2 + 100 = 102 < INF → True
→ dp[2][4] = 102
조건2: dp[1][4] + 100 < dp[2][1]?
INF + 100 = INF < 2 → False
| t | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 2 | 0 | 2 | 5 | 14 | 102 |
| t | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 3 | 0 | 2 | 5 | 14 | 102 |
조건1: dp[2][0] + 2 < dp[3][1]?
0 + 2 = 2 < 2 → False
조건2: dp[2][1] + 2 < dp[3][0]?
2 + 2 = 4 < 0 → False
조건1: dp[2][1] + 3 < dp[3][2]?
2 + 3 = 5 < 5 → False
조건2: dp[2][2] + 3 < dp[3][1]?
5 + 3 = 8 < 2 → False
조건1: dp[2][2] + 4 < dp[3][3]?
5 + 4 = 9 < 14 → True
→ dp[3][3] = 9
조건2: dp[2][3] + 4 < dp[3][2]?
14 + 4 = 18 < 5 → False
조건1: dp[2][3] + 5 < dp[3][4]?
14 + 5 = 19 < 102 → True
→ dp[3][4] = 19
조건1: dp[2][0] + 10 < dp[3][2]?
0 + 10 = 10 < 5 → False
조건2: dp[2][2] + 10 < dp[3][0]?
5 + 10 = 15 < 0 → False
조건1: dp[2][1] + 100 < dp[3][4]?
2 + 100 = 102 < 19 → False
조건2: dp[2][4] + 100 < dp[3][1]?
102 + 100 = 202 < 2 → False
| t | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 3 | 0 | 2 | 5 | 9 | 19 |
ans = dp[3][4] = 1919