Toptree 導入編

みんな日本語記事を待っていたはず....!

toptreeがどんな感じで動いているのかを書いてみます

実装はここにあります

https://github.com/niuez/toptree-rust

0. toptree is なに

toptreeはlink-cut treeの上位互換です. 木を切ったりつなげたり, パスのクエリを処理したり, 木上の二分探索ができたりします

今回はそのベースとなる構造の話です

1. Compress Rake

木をまとめる

ここで言う木は, toptreeが表す木のことです. 曖昧にならないようにこのことをreal treeと呼ぶことにします. (木を木で表現するの文章が曖昧になりがち)

toptreeでは, つながっている2つの辺をまとめる操作を繰り返したものを表現した木です. 1つの辺にまとめ上げることでパスを表現します.

まとめる操作は2つあり, それぞれCompress, Rakeといいます.

f:id:niuez:20190804184030p:plain

具体的に

こんな感じでまとめていきます

f:id:niuez:20190804184116p:plain

このまとめていく操作を木で表現するのがtoptreeです.

Compress Tree

例えば, a - c - bという一直線のreal treeを扱う時, 辺accbをcompressをします.
これをtoptreeで表現すると

f:id:niuez:20190804184140p:plain

四角は辺を表すノードです. Edge Nodeといいます. toptreeでは, Edge Nodeを葉にします. 丸はcompressした後の辺を表すノードです. Compress Nodeといいます.
Compress Nodeが節, Edge Nodeが葉のこの木をCompress Treeといいます.
重要なのは, Compress Treeの根がパスの端点を結ぶ, Compressされた辺を表しているということです. またそのcompressされた辺の端点は必ず次数が1です.

0 - 1 - 2 - 3 - 4 - 5 というreal treeをtoptreeで表すと, 一例としては以下のようになります.

f:id:niuez:20190804184203p:plain

辺の向き付け

ここで葉のEdge Nodeの順番がパスの辺の順番になっている点に注意してください.

toptreeでは辺の向きに注意して操作しないとダメです.
僕の実装では, 0 - 1 - 2 - 3 - 4 - 5のtoptreeを0 -> 1 -> 2 -> 3 -> 4 -> 5と向き付けると解釈しています. 以後のreal treeの図では向き付けしたものを用います.

compress, rakeを, 同じ向きのものをまとめる操作と解釈することにしましょう. すると, compress, rakeを向き付けたreal treeについて改めて考えると以下のようになります.

f:id:niuez:20190804184231p:plain

上の具体的にで示したreal treeはこんな感じで向き付けすると同じようにまとめる操作ができるはずです.

f:id:niuez:20190804184250p:plain

Rake Tree

では一直線ではないreal tree, 例えばこれはどうやってtoptreeにするのでしょうか.

f:id:niuez:20190804184307p:plain

ここでrakeを使います.

f:id:niuez:20190804184324p:plain

?????????????????????

14を追加したreal treeをtoptreeにすると

f:id:niuez:20190804184342p:plain

ひし形のノードはcompressと同じように察せるはずです. 3141をrakeしたものを表現しており, ひし形のノードをRake Nodeといいます.
また, Rake Nodeが節, Compress Treeの根が葉の木をRake Treeといいます.

図では, Compress Nodeに今までの左右の子と, 赤の線でつながった子があります.
赤の線でつながった子は, Compress Nodeの右の子とrakeされるノードです.

このように3分木にしてreal treeの情報を持ちます.

具体例のtoptree

こんな感じになります
四角はEdge Node, 丸はCompress Node, ひし形はRakeNodeです.

f:id:niuez:20190804184401p:plain

Compress Tree(青の点線で囲った部分), Rake Tree(赤の点線で囲った部分)はそれぞれここです

f:id:niuez:20190804184413p:plain

Splice

重要なのは, Compress Treeの根がパスの端点を結ぶ, Compressされた辺を表しているということです.

f:id:niuez:20190804184307p:plain

この木のtoptreeをもう一度見てみます.

f:id:niuez:20190804184324p:plain

0 - 1 - 2がこのtoptreeの主役のパスになっています.

でも, 3 - 1 - 2を主役にしたいときもあるはずです. それは, 3101を入れ替えることで達成できます.

f:id:niuez:20190804184546p:plain

0 - 1 - 3にを主役にしたいときもあるはずです. それは, 3112の向きを反転させてから入れ替えることで達成できます.

f:id:niuez:20190804184558p:plain

この, Rake Treeの葉のノードと, Compress nodeの子を入れ替えてCompress Nodeの表すパスを変える操作をspliceといいます.

Splay

Splay Treeを知っていますか? wikipediaを見て

Splay Treeでは, splayという木を回転させてノードを根に持ってくるという操作をします. まあwikipediaみて
toptreeで扱っている木は, 葉木です. 葉はsplayできないことに注意しましょう.

Handle

spliceをするとパスを変形できることはわかりましたが, 具体的にどのノードをspliceすると良いのでしょうか?

それを示すのがhandleという概念です. handleは各頂点に対して割り振られるもので, toptree上のCompress/Edge Nodeが割り振られます.

具体的には, 下のルールで構成します.

  1. Compress Node abの左右の子がac, cbのとき, 頂点chandleはCompress Node ab
  2. Compress/Edge Node abの親が
  3. いない(toptreeの根): 頂点a, bのhandleはCompress Nodeab
  4. それ以外(Rake Treeの葉になっている): 頂点ahandleはCompress Node ab

具体例を見たほうが早い気がします.

f:id:niuez:20190804184656p:plain

頂点0, 5はルール2., 8, a, b, cはルール3., それ以外は1.です.

今はとりあえずこういうものとしておくのがいいと思います.(あとで大活躍します.)

また頂点vに対して, N_vvhandleのNodeとします. (上の図で言えばN_2はtoptreeの根です)

Expose

exposeという操作を導入したいと思います. (これが超本質)
任意の頂点vhandleをtoptreeの根にするのがexposeです.

先にどうやってexposeするか書いてしまいます.

expose(v) 1. N_vをCompress Tree上でsplayする 2. N_vの親が - いない: N_vはtoptreeの根になったのでexpose終了 - Compress Node: そのCompress Nodeをnとおく - Rake Node: そのRake Nodeをrとおく, rをRake Tree上でsplayし, rの親をnとおく(nはCompress Nodeになります) 3. nをCompress Tree上でsplay 4. nの左のノードとN_vを入れ替える 5. N_vがEdge Nodeのとき, N_vnにする 6. 1に戻る.

1. splay(N_v)

N_vの属しているCompress Tree上でN_vを根にします.

2.

これがちょっとむずかしいです.

親がいない場合は目的達成なので終了です.

Compress Nodeの場合, こんな状態です.

f:id:niuez:20190804184716p:plain

Rake Nodeの場合, 例えばこんな状態です.

f:id:niuez:20190804184730p:plain

splay(r)をすると, こうなります.

f:id:niuez:20190804184742p:plain

3. splay(n)

4で行う操作を簡単にするために行います. なんで簡単になるかはsoft_exposeで解説したいと思います.

4. splice(N_v)

入れ替えます.

f:id:niuez:20190804184801p:plain

f:id:niuez:20190804184807p:plain

5.

これは何かというと, N_vがEdge Nodeのとき, spliceするとvhandleの位置が変わります. これに対応するためです.

6.

これで, N_vをtoptreeの根にすることができます.

Soft Expose

soft_exposeは任意の頂点v, u間のパスのCompress Node vuを作る操作です!(やっとここまできた)
こんな形にtoptreeを変形します.

f:id:niuez:20190805073700p:plain

(8/5 なんか頭悪い画像になっていたので修正しました)

手順を先に言ってしまいます

soft_expose(v, u) 1. expose(v) 2. N_vN_wが - 同じ: toptreeの根はvuuvなので, uvであれば反転する. soft_exposeおわり - 違う: 続く 3. N_vをguardする(????) 4. expose(w) 5. N_vのguardを外す 6. N_vから見てN_uが右側なら, 反転させる. 7. おわり

toptreeの根をguardするとは, splay操作があってもtoptreeの根を変えさせないようにすることです.
これは, N_vをtoptreeの根にした後, N_vの左側にN_wを持ってくる必要があり, N_vが根であり続ける必要があるからです.

guardされているときのspliceの操作が少し違います.

nの親がguardされていて, 親から見て左側にある場合, spliceはnの左の子と交換しないといけません.

f:id:niuez:20190804184839p:plain

しかし, 親から見て右側にある場合, spliceはnの右の子と交換しないといけません.

f:id:niuez:20190804184855p:plain

これはtoptreeの, 葉がパスの辺の順番になっているルールに違反するからです.(toptree壊れる)

Path Query

soft_exposeができるようになると, パスに関するクエリを処理することができます.
パスの長さとか, パス中の辺の長さの最大値とかです.

各ノードに情報をもたせて, セグ木みたいに左の子の情報と右の子の情報を演算するみたいな感じです. これをすると, soft_expose(v, w)をしてvwを見た時にパスv-wについての演算結果が求まるはずです. やったね.

ひとまず終了...

link, cut, select, 各種クエリとかは後日にします... 疲れた...

niuez.hatenablog.com link cutかいた

noshi91.hatenablog.com

僕が書くのサボった厳密な話をnoshi91さんが書いています こちらも読んでください