모든 출발지 - 목적지 쌍의 최단 경로를 구하는 알고리즘
한 번 돌리고 나면 일종의 최단 경로 사전이 만들어진다고 볼 수 있다.
그냥 BFS
를 모두 비교해봐야 함
그냥 플로이드-워셜(음의 가중치가 있어도)
시간 복잡도가 팩토리얼로 나온다 ≡ 사실상 사용 불가능
이 만 돼도 회의 연산이 발생한다.
그래서 이 크다면 인접 리스트 + Priority queue + Dijkstra로 (출발지 바꿔 가면서) 돌리는 게 낫다.
대충 일 때까지만 쓸 만 한 듯.
allPairsShortest(d[1..n][1..n]):
FOR k ← 1 TO n
FOR i ← 1 TO n
IF i = k
CONTINUE
FOR j ← 1 TO n
IF j = k OR j = i
CONTINUE
d[i][j] ← MIN(d[i][k] + d[k][j], d[i][j])
d[i][j]: 직전까지 계산된 i에서 j로의 최소 비용d[i][j] ← MIN(d[i][k] + d[k][j], d[i][j])
d[i][j] ← MIN(d[i][k] + d[k][j], d[i][j])에서 correctness가 보장되려면
d[i][k]는 i에서 k로 갈 때 1 ~ (k - 1)을 경유지로 고려한 최소비용이어야 한다(d[k][j]도 마찬가지).
즉 d[i][j]를 갱신하는 시점에 이미 d[i][k]와 d[k][j]는 각각 i -> k, k -> j로 가는 최소 비용이어야 한다.
d[i][j]는 점진적으로 최적화된다. 이때 k는 '이번 단계에서 경유지로 사용을 허락할 노드'를 의미한다.
즉 다음과 같이 최적화 된다.
1. k = 1: 기존 경로와 1번 노드만을 경유지로 사용했을 때의 경로 중 더 짧은 경로를 찾는다.
즉 i -> j와 i -> 1 -> j를 비교한다.
2. k = 2: 현재까지의 최단 경로와 2번 까지 경유지로 사용했을 때의 경로 중 더 짧은 경로를 찾는다.
즉 앞에서 비교된 i -> j, i -> 1 -> j와 함께 i -> 2 -> j를 비교하게 된다.
...
n. k = n: 기존 경로와 n번 노드까지 경유지로 사용했을 때의 경로 중 더 짧은 경로를 찾는다.
즉 i -> j, i -> 1 -> j, ..., i -> n -> j 를 비교하게 된다.
즉 k는 알고리즘의 진행 단계를 나타낸다.
k번째 단계가 끝나면 {1, 2, ..., k}노드를 각각 경유지로 사용한 경우의 경로를 비교하고, 그 중 최단 경로가 d[i][j]에 저장된다.
allPairsShortest2(d[1..n][1..n]):
FOR i ← 1 TO n
FOR k ← 1 TO n
IF i = k
CONTINUE
FOR j ← 1 TO n
IF j = k OR j = i
CONTINUE
d[i][j] ← MIN(d[i][k] + d[k][j], d[i][j])
이 경우 d[i][j]를 갱신하는 시점에, i에서 k로 가는 최단 경로는 고려되지 않은 상태에서 비교하게 된다.
즉 부분 경로(경유지를 거치는 경우의 경로)의 최단 경로가 계산되지 않은 상태에서 비교하게 되기 때문에 correctness가 보장되지 않는다.
그래서 경유지 → 출발지 → 목적지와 경유지 → 목적지 → 출발지는 되지만 그 외의 경우는 불가능하다.
distance = new int[N][N];
for(int i = 0; i < N; ++i) {
for(int j = 0; j < N; ++j) {
distance[i][j] = sc.nextInt();
//자기자신으로의 인접 정보가 아니고 인접해있지 않다면 INF로 채우기
if(i != j && distance[i][j] == 0) { //Sentinel
distance[i][j] = INF;
}
}
}
// 출발지-->경유지-->목적지로 3중 루프 돌리면 오답
// 경유지-->출발지-->목적지로 3중 루프 돌려야 정답
for(int k = 0; k < N; ++k) {
for(int i = 0; i < N; ++i) {
if(i == k) continue; // 출발지와 경유지가 같다면 다음 출발지
for(int j = 0; j < N; ++j) {
if(i == j || k == j) continue; // 경유지와 목적지가 같거나 출발지가 곧 목적지라면 패스
if(distance[i][j] > distance[i][k] + distance[k][j]) {
distance[i][j] = distance[i][k] + distance[k][j];
}
}
}
}
import java.io.*;
import java.util.*;
public class Main {
static boolean[][] isAdjacent;
static int n, m;
public static void main(String[] args)
throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine(), " ");
n = Integer.parseInt(st.nextToken());
m = Integer.parseInt(st.nextToken());
isAdjacent = new boolean[n][n];
for(int idx = 0; idx < m; ++idx) {
st = new StringTokenizer(br.readLine(), " ");
int i = Integer.parseInt(st.nextToken());
int j = Integer.parseInt(st.nextToken());
isAdjacent[i - 1][j - 1] = true;
}
for(int k = 0; k < n; ++k) { //경유지
for(int i = 0; i < n; ++i) { //출발지
//경유지와 출발지가 같은 경우
if(i == k) continue;
for(int j = 0; j < n; ++j) { //목적지
//출발지와 목적지 또는 경유지와 목적지가 같은 경우
if(i == j || k == j) continue;
if(isAdjacent[i][k] && isAdjacent[k][j]) {
isAdjacent[i][j] = true;
}
}
}
}
//count = 각 i에 대해 노드 i와 연결된 노드의 수
int answer = 0;
for(int i = 0; i < n; i++) {
int count = 0;
for(int j = 0; j < n; j++) {
if(isAdjacent[i][j] || isAdjacent[j][i]) {
++count;
}
}
if(count == n - 1) {
++answer;
}
}
System.out.print(answer);
br.close();
}
}
import sys
input = sys.__stdin__.readline
def main():
n, m = map(int, input().rstrip().split())
INF = n**2
dist = [[0 if i == j else INF for j in range(n)] for i in range(n)]
for _ in range(m):
a, b = map(int, input().rstrip().split())
dist[a - 1][b - 1] = 1
dist[b - 1][a - 1] = 1
# Floyd - Warshall
for k in range(n):
for i in range(n):
if i == k:
continue
for j in range(n):
if i == j or j == k:
continue
dist[i][j] = min(dist[i][j], dist[i][k] + dist[j][k])
# 케빈 베이컨 수 계산
answer = n # Sentinel
min_cb = n**2 # Sentinel
for i in range(n):
cb = 0
for j in range(n):
cb += dist[i][j]
if min_cb > cb:
min_cb = cb
answer = i
print(answer + 1)
main()