Processing math: 100%

最小共通祖先

Lowest Common Ancestor, LCA, 最近共通祖先などといわれる。「共通祖先」で検索すると生物進化方面の用語が混じって出てくるので記事をやや探しづらい。

根付き木において、頂点 a と頂点 b を根に向かって辿っていった時、最初に合流する頂点のことを指す。

     1
    /  \
   2  3    5と6のLCAは2
  / \
 4 5       3と4のLCAは1
 |
 6          1と2のLCAは1

元は根付き木でなくても、適当な頂点を根としてLCAを求められるようにしておくと、様々な用途に使える。(追加で前計算する情報が必要になる場合がある)

  • 任意の2点間距離
  • 任意の2点間を結ぶパス上の最小コスト辺、最大コスト辺
  • 任意の2頂点を含む部分木の要素数

解法

求め方は複数のアルゴリズム・実装が知られている。

  • Euler Tour + Range Minimum Query
  • ダブリング
  • Schieber-Vishkin algorithm

EulerTour + RmQ

オイラーツアー

オイラーツアーは、深さ優先探索で通りかかった順番に頂点を記述する方法。N1 辺を行き帰りで2回ずつとおるので、出発点と合わせて長さ 2N1 の配列になる。

     0
    /  \   
   1  2    0, 1, 3, 5, 3, 1, 4, 1, 0, 2, 0
  / \
 3 4
 |
 5

この変換により、木の祖先・子孫関係を1次元配列で持つことができるため、 1次元配列で扱える様々なデータ構造を利用できる(BIT、セグメント木など)

オイラーツアーを用いたLCA

LCAを求めるには、オイラーツアーで通りかかった順番に頂点番号と深さを記録していく。 また、その過程で各頂点が最初に現れた位置も記録していく。 (実際には、最初に現れた位置でなく、最初~最後までのどこかの位置であればよいが、通常は最初に記録しておくのが一番やりやすい)

  • eular_tour[i] = (d, v)
    • i 番目に通りかかる頂点の(深さ, 頂点番号)
  • first_appear[v] = i
    • 頂点 v がeular_tour上で初めて現れるindex
     0
    /  \   eular_tour = [d: 0, 1, 2, 3, 2, 1, 2, 1, 0, 1, 0
   1  2                v: 0, 1, 3, 5, 3, 1, 4, 1, 0, 2, 0]
  / \
 3 4     first_appear = [0, 1, 9, 2, 6, 3]
 |
 5

こうすると、頂点 ab のLCAは、以下で求められる。

  • それぞれが eular_tour 上ではじめて現れるindex pa,pb を取得する
    • pa<pb と仮定する
  • euler_tour上で、区間 [pa,pb] 内から、depthが最小となる頂点を取得する
  • そいつがLCAである(このような頂点はただ1つ存在する)
頂点(a, b) = (5, 4) のLCAを求める
  → pa = 3, pb = 6
eular_tour[3:6] = [(3, 5), (2, 3), (1, 1), (2, 4)]
この中で最小は (1, 1) なので、深さ1の頂点「1」がLCA

もし、LCAの頂点番号は不要で、深さだけが求められればいい場合、eular_tour で管理するのは d のみでよい。

区間最小値

ここで、区間の最小値を求めるクエリRmQが発生する。以下のアルゴリズムなどで解ける。

  • 平方分割
  • セグメント木
  • Sparse Table

基本的に更新は不要なので、前処理 O(NlogN)、クエリ O(1) で処理できるSparse Tableが高速。

ダブリング

ダブリングは、

  • 前計算で1個先、2個先、4個先、8個先、… の結果を求めていく。たとえば8個先は「4個先の4個先」のように直前の結果を利用できる
  • 前計算結果を利用すると、たとえば21個先は「1個先の4個先の16個先」とたかだか logN 回の遷移で求められる

というアルゴリズム。これをLCAを求めるのに適用する。

各頂点から、1個先の親、2個先の親、4個先の親、8個先の親、…を記録していく。(便宜的に根の親は根とする)

  • double[k][v] = u
    • 頂点 v から 2k 個先の親の頂点番号 u
  • depth[v] = d
    • 頂点 v の深さ d

k が小さい方から順に全頂点を埋めていくと、double[k][v]=double[k1][double[k1][v]]] と直前の結果を利用できる。 最も深い頂点が1発で根にたどり着くような k で止める。

a,b の深さを合わせる

作業用の変数として at,bt を用意し、atdepth[a]btdepth[b] とする。

bt の方が深いとし、深さの差が38(=21+22+25)だったとする。

bt = double[1][bt]
bt = double[2][bt]
bt = double[5][bt] → これでbtとatで深さが揃った

この時点で at=bt であれば、LCAは a である。以下、それ以外の場合を考える。

同階層ながら互いに異なる、最も浅い祖先を求める

at,bt の深さは揃った。そこから1発で根を通り越さない最大の k から、k を1ずつ減らしながら以下を繰り返し行う。

  • double[k][at] != double[k][bt] なら、2k 先の親に移動
    • at ← double[k][at], bt ← double[k][bt]
  • 同じなら何もしない
  • k を1減らして繰り返す
    • k ← k-1

k=0 までやったら、at,bt の1つ親がLCM。

実装例(Python)

ついでに求められる情報

2点間辺数、2点間距離

頂点 a,b を結ぶパスは、c=lca(a,b) として、a から c までは木を遡上し、c から b まで下ることとなる。

根から各頂点への距離 dist[v] を記録しておけば、簡単な計算で2点間距離を求めることができる。

a,b の距離: dist[a]+dist[b]2dist[c]

a から根まで遡上し、根から b まで下る、という経路の内、根~c の往復分が無駄である」と考えるとよい。

     root
     /  \
    c    e
   / \   ...
 ...  b
  /\
 a  d

2点間を結ぶパス中の1辺の最大(最小)コスト

こちらは、もう少し面倒になる。

max(min)には逆演算がないので、距離と同様に根から各頂点を結ぶパスの最大コストを記録してもダメ(その辺が根~c に位置する場合、結局求めたい値はわからない)。

LCAをダブリングで求める手法と同じようにすればよい。この場合、LCA自体を求めるアルゴリズムもダブリングの方がわかりやすい。

  • maxp[k][v]
    • v から根に向かって 2k 頂点を遡上するパスの中での最大コスト

ac 間の最大コストは、ac の深さの差が例えば d=21+22+25 であれば、以下を求めればよい。

  • max(maxp[1][v],maxp[2][v2],maxp[5][v6])
  • ただし、vp=v から p 個遡上した頂点

LCAもダブリングで実装していれば、これはLCAを求める過程で同時に求められる。

b からも同じことをして、その大きい方が、求める答えとなる。

バリエーション

二分木におけるLCA

木の構造が定まっているので、bit演算で計算できる。手順はダブリングと似ている。

根を1とし、頂点 x の子を頂点 2x,2x+1 とする。こうすると、頂点番号のbit長が、その頂点の深さと一致する。

  1. a,b のbit長が異なる場合、長い方(深い方)を、短い方にあわせて右bitshiftする
    1. b の方が長く、a に合わせた結果を b とする
  2. 上位bitから、ab がどこまで一致するか見ていく。下から数えて i 桁目で食い違ったとする
  3. ai 桁 右bitshiftしたものが答え
    1            0001
   /  \          /    \
  2  3      0010     0011
  /\  /\      / \       / \
 4567  0100 0101 0110 0111

3と4のLCA
1. 4(0100)を3(0011)にあわせて2桁にする → (0010)
2. (0010)と(0011)を先頭から一致するまで見ていく。下1桁で食い違う
3. (0010)を1桁 右bitshiftした (0001) がLCA
programming_algorithm/graph_theory/lowest_common_ancestor.txt · 最終更新: 2020/05/10 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0