본문으로 건너뛰기
김신건의 로그

Directed MST

· 수정 · 📖 약 2분 · 934자/단어 #algorithm #graph #mst #directed-graph #arborescence
Directed MST, Arborescence, Chu-Liu Edmonds, 최소 비용 신장 가지치기

정의

Directed MST (Arborescence) 는 유향 그래프와 루트 r 이 주어졌을 때, r 에서 모든 정점으로 도달 가능 한 간선들의 최소 가중치 부분집합을 구하는 문제. 무향 MST (Kruskal/Prim) 의 유향 버전.

각 정점이 루트가 아닌 한 정확히 한 개의 들어오는 간선을 선택. 사이클이 생기면 사이클 contraction 으로 해결. Chu-Liu / Edmonds 알고리즘이 고전 (1965/1967).

문제 상황과 동기

유향 그래프에서 루트 r 로부터 모든 정점에 도달하는 최소 비용 가지치기 (arborescence) 를 찾고 싶다. 무향 그래프는 Kruskal / Prim 으로 O(E log V) 이지만, 유향 그래프는 간선 방향 제약 + 사이클 처리 가 필요.

naive 하게 각 정점마다 최소 비용 들어오는 간선을 선택 하면 사이클이 생길 수 있다. 예: a ← b ← c ← a (사이클). 이 경우 사이클을 한 덩어리로 묶어 contract 한 뒤, 축소된 그래프에서 재귀적으로 풀고, expand 시 사이클 내 한 간선을 제거한다.

핵심 통찰: 사이클이 없으면 각 정점의 최소 간선이 답. 사이클이 있으면 사이클 전체를 슈퍼노드로 축소, 비용 보정 후 재귀. 펼칠 때 사이클 중 하나만 끊는다. O(VE) 또는 meldable heap 으로 O((V+E) log V).

PS 에서는 “의존성 트리 최소 비용”, “트리 구조 최적화” 같은 모델링 문제에 등장. ICPC / IOI 급에서 드물지만 표준 라이브러리 없어 직접 구현 필요.

시각화

핵심 아이디어 (Chu-Liu/Edmonds)

  1. 루트가 아닌 각 정점에 대해 들어오는 간선 중 최소 비용 을 선택
  2. 선택된 간선들로 그래프 구성 → 사이클이 없으면 종료, 답
  3. 사이클이 있으면 사이클을 한 정점으로 contract, 사이클 비용 보정 후 재귀
  4. 펼치기 (uncontract) 단계에서 어떤 간선을 끊을지 결정
chuLiuEdmonds(G, root):
    in_edge[v] = min cost incoming edge for each v ≠ root
    if 형성된 그래프에 사이클 없음: return in_edge
    cycle 들을 contract -> G'
    sub = chuLiuEdmonds(G', root')
    expand cycles, fix one edge in each cycle
    return sub + cycle edges - replaced

O(VE) 가 기본. Tarjan 의 meldable heap 기반 구현은 O((V+E) log V).

작은 예시

그래프:
  r --5--> a --3--> c
   \--2--> b --1--/
           b --4--> a

step 1: 각 정점의 최소 간선 선택
  a: (b, a, 4)
  b: (r, b, 2)
  c: (b, c, 1)

step 2: 그래프 구성
  r -> b -> c
       b -> a
  
  사이클 없음 → 답: {(r,b,2), (b,a,4), (b,c,1)} = 7

---

반례 (사이클 발생):
  r --5--> a --1--> b
           a <--2-- b

step 1: 각 정점 최소 간선
  a: (r, a, 5)
  b: (a, b, 1)

step 2: 그래프 구성
  r -> a -> b
       a <- b  ← 사이클!

step 3: contract {a, b} -> 슈퍼노드 X
  비용 보정: 외부에서 X 로 들어오는 간선은
             원래 비용 - (해당 정점이 사이클 내에서 선택한 간선 비용)
  예: (r, a, 5) -> (r, X, 5 - 0) = 5  (a 는 사이클 시작점)

step 4: 재귀 풀고 expand
  사이클 내 한 간선 제거 → (a, b, 1) 유지, (b, a, 2) 제거

복잡도

알고리즘시간
기본 Chu-Liu/EdmondsO(VE)
Tarjan/Gabow O((V+E) log V)O((V+E) log V)
공간O(V + E)

응용

1. 트리 / 가지치기 모델링

“한 모듈을 다른 모듈로부터 만들 수 있고, 비용 최소” 같은 의존성 그래프.

2. 외계 모드 LCT 연습

Dynamic Tree 의 결과를 검증할 때 brute reference.

3. ICPC 응용

대형 contest 에서 정점 만 단위 규모. 표준 라이브러리 (atcoder library) 가 없으므로 직접 구현.

구현

O(VE) 기본 Chu-Liu/Edmonds

// O(VE), 사이클 detect + contract + 재귀
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

struct Edge { int u, v; long long cost; };

long long directed_mst(int n, int root, vector<Edge> edges) {
    // in[v] = v 로 들어오는 최소 간선 인덱스
    vector<int> in(n, -1);
    for (int i = 0; i < (int)edges.size(); i++) {
        int v = edges[i].v;
        if (v == root) continue;
        if (in[v] == -1 || edges[i].cost < edges[in[v]].cost) {
            in[v] = i;
        }
    }
    
    // 사이클 detect (DFS)
    vector<int> vis(n, -1), cycle_id(n, -1);
    int num_cycles = 0;
    for (int i = 0; i < n; i++) {
        if (i == root || vis[i] >= 0) continue;
        int v = i;
        while (vis[v] < 0 && v != root) {
            vis[v] = i;
            if (in[v] == -1) break;
            v = edges[in[v]].u;
        }
        if (vis[v] == i) {
            // cycle found
            int w = v;
            do {
                cycle_id[w] = num_cycles;
                w = edges[in[w]].u;
            } while (w != v);
            num_cycles++;
        }
    }
    
    if (num_cycles == 0) {
        // no cycle → sum cost
        long long total = 0;
        for (int v = 0; v < n; v++) {
            if (v != root) total += edges[in[v]].cost;
        }
        return total;
    }
    
    // contract cycles
    vector<int> id(n);
    iota(id.begin(), id.end(), 0);
    int new_n = 0;
    for (int v = 0; v < n; v++) {
        if (cycle_id[v] >= 0) id[v] = n + cycle_id[v];
        else id[v] = new_n++;
    }
    new_n += num_cycles;
    
    vector<Edge> new_edges;
    for (auto& e : edges) {
        int nu = id[e.u], nv = id[e.v];
        if (nu == nv) continue;  // 사이클 내부 간선
        long long new_cost = e.cost;
        if (cycle_id[e.v] >= 0) {
            // 사이클로 들어오는 간선: 비용 보정
            new_cost -= edges[in[e.v]].cost;
        }
        new_edges.push_back({nu, nv, new_cost});
    }
    
    long long cycle_cost = 0;
    for (int v = 0; v < n; v++) {
        if (cycle_id[v] >= 0 && in[v] >= 0) {
            cycle_cost += edges[in[v]].cost;
        }
    }
    
    return cycle_cost + directed_mst(new_n, id[root], new_edges);
}

실전에서는 meldable heap (pairing heap) 기반 O((V+E) log V) 구현을 권장 (koosaga, kactl).

함정

1. 루트로 도달 불가능한 정점

루트에서 모든 정점에 도달 가능해야 답 존재. 사전에 BFS 로 확인.

2. 사이클 contraction 의 비용 보정

새 contract 노드로 들어오는 간선 (u, w) 의 비용은 c(u,w) - c(in_edge[w]) 로 보정해야 옳다. 빠뜨리면 답이 틀림.

3. 구현 길이

기본 O(VE) 도 200 ~ 300 줄. 검증된 레퍼런스 (koosaga, kactl) 추천.

BOJ 연습 문제

번호제목링크
BOJ 16127미생물 키우기kokoa-lab
BOJ 19264Hung Fukokoa-lab
BOJ 9582Dictionarykokoa-lab

다른 출처 연습 문제

출처제목링크
Library CheckerDirected MSThttps://judge.yosupo.jp/problem/directedmst

참고

이 글의 용어 (5개)
정렬 알고리즘algorithm
정의 정렬 (sort) 은 원소들의 컬렉션을 어떤 전순서 (total order) 기준으로 재배열하는 것. 알고리즘 입문의 정석 주제이자, 데이터베이스·검색·통계 등 모든 시스템…
Dominator Treealgorithm
정의 유향 그래프와 시작점 가 주어졌을 때, 정점 가 정점 를 dominate 한다는 것은 에서 로 가는 모든 경로가 반드시 를 지난다 는 뜻. Dominator Tree 는 모…
Dynamic Tree (Link/Cut Tree, Euler Tour Tree, Top Tree)algorithm
정의 Dynamic Tree 는 트리에 간선 추가 (link) / 제거 (cut) 가 섞이는 환경에서 경로 / 서브트리 집계 쿼리 를 O(log N) 에 처리하는 자료구조 가족.…
Offline Incremental SCC, Offline Dynamic MSTalgorithm
정의 Offline Incremental SCC 는 간선이 시간 순서대로 추가만 되는 유향 그래프에서, 두 정점이 같은 SCC 에 처음 속하는 시각 을 모든 쌍에 대해 오프라인으…
Push-Relabel, Cost Scalingalgorithm
정의 Push-Relabel 은 증가 경로 (augmenting path) 기반 의 Ford-Fulkerson / Dinic 과 달리 각 정점의 잉여 흐름 (excess) 을 국…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기