Processing math: 100%

木のオイラーツアー

グラフ理論の木の表現方法の1つ。

「根から全ての辺を2回ずつ通って根に帰ってくる」パスを指す。

少し混同しやすい語として、グラフで「オイラー路(Eulerian Trail)」というと、全ての辺を1度ずつ通るパスを指す。

オイラーツアーは、木の無向辺それぞれを、双方向の2本の有向辺に変換したグラフ上でのオイラー路、とも言える。

文脈によっては、パスで訪れる頂点や辺の番号を順に記録した列そのものを指すこともある。

    ⓪        オイラーツアーの頂点通過順
 a/b|\c     0,1,4,5,4,6,4,1,0,2,0,3,7,3,8,3,0
 ① ② ③
d|   e/\f    オイラーツアーの辺通過順
 ④   ⑦⑧     a,d,g,g,h,h,d,a,b,b,c,e,e,f,f,c
g/\h          (または、ツアーを開始・終了することを示す便宜的な値を加えて)
⑤⑥         X,a,d,g,g,h,h,d,a,b,b,c,e,e,f,f,c,X

利点

こんな表現をして何が嬉しいか?

一般に、木に対するクエリ問題は更新に弱い。

頂点や辺が何らかの値を持っていて、パスや部分木の合計値といった取得クエリのみが飛んでくる場合には、 前計算で高速に対応できることもある。
しかし、値の更新クエリもある場合、その反映も同時にこなすのは、なかなか難しい。

オイラーツアーは、木構造を列で表現するため、セグメント木など「列」に対する便利なデータ構造を適用できる。
それによって、更新も取得も O(logN) とかでできるような状態の持ち方ができる。

他に同様の利点があるデータ構造に、上記のリンクにもあるが HL分解 がある。

性質

部分木を列の連続区間で表せる

1辺を2回通るということは、行くときと帰るときで2回使われるので、他に無駄な往復はできない。

ある頂点 v を根とする部分木に注目すると、以下のようにするしかない。

  • ① 部分木に入る(v の親→v の辺を通過、1回目)
  • ② 部分木の全ての頂点をオイラーツアーで巡る
  • ③ 部分木を出ていく(vv の親 の辺を通過、2回目)

なので、オイラーツアーでは、部分木は列の連続区間として現れるし、異なる部分木は互いに交わらない。

    ⓪        オイラーツアーの頂点通過順
 a/b|\c     0,1,4,5,4,6,4,1,0,2,0,3,7,3,8,3,0
 ① ② ③       ~~~~~~~~~~~~~       ~~~~~~~~~
d|   e/\f      ①を根とする        ③を根とする
 ④   ⑦⑧      部分木の            部分木の
g/\h            オイラーツアー      オイラーツアー
⑤⑥

特に、各頂点が最初に現れる in[v] と最後に現れる out[v] を前計算しておけば、 その区間 [in[v],out[v]] が、v を根とする部分木を表す区間となる。

オイラーツアーで解ける問題例

大きく分けて、セグメント木で管理する実装と、split-mergeが可能な n 分木(平衡二分木など)で管理する実装がある。
n 分木では実装が複雑ながら、辺の追加・削除といった更新にも対応できる。

  • 取得クエリ
    • 根から v までのパスに含まれる頂点・辺の値の合計
    • v を根とする部分木内の頂点・辺の値の合計
    • 最小共通祖先(LCA, Lowest Common Ancestor)
  • 更新クエリ(セグメント木・遅延評価セグメント木)
    • 頂点・辺に値を加算・値で更新
    • 部分木内の頂点・辺に一定値を加算
  • 例題
  • 更新クエリ(n 分木)
    • (上記に加え、)
    • 辺の追加(元の状態は森であり、クエリ時点で同じ連結成分内にある2頂点間に辺は張られない前提)
    • 辺の削除
  • 例題

実装例

頂点や辺に値はあるか、何を更新して何を求めたいか、によって必要な情報がちょっとずつ異なり、 汎用的なライブラリに落とし込みにくいのよね。

LCA(最小共通祖先)

問題設定

  • N 頂点の木が与えられる(頂点番号は 1N
  • Q 個のクエリが与えられる
    • i 番目のクエリでは、頂点 ui,vi の最小共通祖先を求めよ

必要な前計算データ

  • sgt_depths: オイラーツアー頂点順の (深さ, 頂点番号) のタプル列を、区間最小値を取得できるセグメント木に載せたもの
  • first_appear: 各頂点がオイラーツアー上で最初に出てくるindex
    • (最初でなくても、どこかしらの出現indexであればよい)

原理とかは、冒頭で挙げたサイトで図付きで説明されているので割愛。

sgt_depths上で first_appear[u]first_appear[v] の区間最小値を取得して、その頂点番号がLCA。first_appear[u]>first_appear[v] の場合もあるので順番に注意。

なお、この問題はダブリングで解くこともできる。(最小共通祖先

Python3

programming_algorithm/graph_theory/eular_tour.txt · 最終更新: 2023/03/24 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0