[競プロ用]最小全域木まとめ


# 最小全域木を求める手法

* プリム法
* クラスカル法

# 使い分け

議論になっていた
https://togetter.com/li/776049

入力形式で使い分ける？

* ノード間のコストが行列で渡される → プリム法
* 2点のノードとそのコストの組み合わせで渡される。 → クラスカル法

https://www.hamayanhamayan.com/entry/2018/09/17/163004

# プリム法

1. 任意のスタート地点から始める。
2. 移動可能なノードのうち最も低コストで移動できるものを選び繋ぐ。
3. 2を繰り返し、全てのノードを繋いだ時点で最小全域木となる。

## どうすればいいか

ノードを繋ぐ度に選択肢が広がっていき、常にその中で最小のものを選択したい。
→ 優先度付きキュー(heapq)を用いる。

heapqにタプルを渡すと一番目の要素で最小値を判断されることを利用する。

## 何がいいのか

O(ElogV)。
フィボナッチヒープを使うとより早い？

## イメージ

https://algo-logic.info/prim-mst/

## 使用例

[AOJ ALDS1_12_A](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ALDS1_12_A&lang=jp)

```python:プリム法
import heapq

def main():
    n = int(input())
    next = []   # 隣接管理のリスト
    for _ in range(n):
        l = list(map(int, input().split()))
        next.append([(c, i) for i, c in enumerate(l) if c != -1])
    visited = [0] * n
    connection = 0
    q = [(0, 0)]    # (cost, v)
    heapq.heapify(q)
    ans = 0
    while len(q):
        cost, v = heapq.heappop(q)
        if visited[v]:
            continue

        # そのノードと繋げる
        visited[v] = 1
        connection += 1
        ans += cost
        
        # 新たに繋げたノードから行けるところをエンキュー
        for i in next[v]:
            heapq.heappush(q, i)
        
        # 全部のノードが繋がったら終了
        if connection == n:
            break
    print(ans)
    return
```

# クラスカル法

1. 全ての辺を重みの小さいものから順に選びとっていく。
2. その過程で閉路を作ってしまうものは捨てる。全ての辺に対してこれを行う。
3. 最終的に選び取った辺のみで最小全域木となる。

## どうすればいいか

1 → 辺のコストを元にソートする。リストの各要素をタプルにしてソートすると1番目の要素でソートされるテクニックを利用。
2 → 「閉路ができる」＝「既に2点が繋がっているところに辺を作る」なのでUnionFind木で確認する。

## 何がいいのか

O(ElogV)。

## イメージ

https://algo-logic.info/kruskal-mst/

## 使用例

[AOJ GRL_2_A](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=GRL_2_A)

```python:クラスカル法
def main():
    V, E = map(int, input().split())
    uf = UnionFind(V)

    # 1の過程
    edges = []
    for _ in range(E):
        s, t, w = map(int, input().split())
        edges.append((w, s, t))
    edges.sort()

    # 2の過程
    cost = 0
    for edge in edges:
        w, s, t = edge
        if not uf.same(s, t):
            cost += w
            uf.union(s, t)
    print(cost)
    return
```

---
追記(6/1)

# 考えること

* 辺の数が膨大になるとO(ElogV)のオーダーでは間に合わないので、非常に辺が多い場合には候補となる辺を減らさないかを考えるのが有力。 → 応用例参照
* 最小全域木のこれらの手法は多重辺があっても使用できる。→ 同じコストの辺を重複してカウントしても問題ない

# 応用例

[ARC076 D - Built?](https://atcoder.jp/contests/arc076/tasks/arc076_b)
1つめ：Eが膨大なケースでは予め減らすことを検討する。
→今回はx座標上でもy座標上でも隣接しない町同士は選ばれないことを利用。
2つめ：x軸、y軸ともに隣接する町に該当する場合には2回カウントされるが気にしない。

```python
def main():
    N = int(input())
    nodes = []
    for i in range(N):
        x, y = map(int, input().split())
        nodes.append((x, y, i))  # 座標に一意性があればタプルのままUnionFindにぶっこめるが、同一座標に町があるケースがあるのでナンバリングする。

    uf = UnionFind(N)
    nodes_sorted_by_x = sorted(nodes)
    nodes_sorted_by_y = sorted(nodes, key=lambda i: i[1])

    edges = []
    for i in range(N - 1):
        dx = abs(nodes_sorted_by_x[i][0] - nodes_sorted_by_x[i + 1][0])
        dy = abs(nodes_sorted_by_x[i][1] - nodes_sorted_by_x[i + 1][1])
        cost = min(dx, dy)
        edges.append((cost, nodes_sorted_by_x[i][2],  nodes_sorted_by_x[i + 1][2]))

    for i in range(N - 1):
        dx = abs(nodes_sorted_by_y[i][0] - nodes_sorted_by_y[i + 1][0])
        dy = abs(nodes_sorted_by_y[i][1] - nodes_sorted_by_y[i + 1][1])
        cost = min(dx, dy)
        edges.append((cost, nodes_sorted_by_y[i][2],  nodes_sorted_by_y[i + 1][2]))

    edges.sort()
        
    ans = 0
    for edge in edges:
        w, s, t = edge
        if not uf.same(s, t):
            ans += w
            uf.union(s, t)
    print(ans)
```

# Appendix

タプルのリストをソートする際のキーを指定する。

```
# 通常はタプルの1番目の要素でソートされる。
sortedByFirstKey  = sorted(listOfTuple)
# Keyにラムダ式を指定し、タプルの2つ目の要素でソートするようにする。
sortedBySecondKey = sorted(listOfTuple, key=lambda i: i[1])
# reverse
sortedBySecondKey = sorted(listOfTuple, key=lambda i: i[1], reverse=True)
```

特に辞書のリストをソートしようとするとエラーするので上記のように`key`を指定してあげる必要がある。
`reverse`で逆順を指定する。`sort()`でも`sorted()`でも使用可能。

---

# 参考

* 蟻本
* https://www.hamayanhamayan.com/entry/2018/09/17/163004
* https://juppy.hatenablog.com/entry/2019/02/16/%E8%9F%BB%E6%9C%AC_python_%E6%9C%80%E5%B0%8F%E5%85%A8%E5%9F%9F%E6%9C%A8%E5%95%8F%E9%A1%8C%EF%BC%91%EF%BC%88heap%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E3%83%97%E3%83%AA%E3%83%A0%E6%B3%95%EF%BC%89_%E7%AB%B6
* https://algo-logic.info/prim-mst/
* https://algo-logic.info/kruskal-mst/
* https://note.nkmk.me/python-dict-list-sort/