Link/Cut Tree(base)
(lct/link-cut-base.hpp)
- View this file on GitHub
- Last update: 2021-04-26 00:32:26+09:00
- Include:
#include "lct/link-cut-base.hpp"
Link/Cut Tree
木の回転・辺の削除・辺の追加などを$\mathrm{O}(\log n)$で行うライブラリ。
概要
忘れないうちに簡潔にまとめる。
情報の持ち方
-
(反転可能Splay Treeの実装は前提知識とする)
-
Link Cut Treeの内部では木を根付き木として管理している
-
重軽分解のようにPreferred EdgeとNormal Edgeの二つに塗り分けられていて、一つの頂点から子に生えるPreferred Edgeは高々1つ
-
Preferred Edgeからなるパス、Preferred Pathの内部の点は深さを比較関数としたSplay Treeに載せられている
- Preferred Pathは内部の実装では双方向にリンクしている。つまり、uの左の頂点がvの場合
u->l == v && v->p == u
が成り立つ
- Preferred Pathは内部の実装では双方向にリンクしている。つまり、uの左の頂点がvの場合
-
Preferred Pathの最も根に近い頂点を
v
と置くと、v
が木全体の根でないときv
の親u
へのパス(Normal Path)が存在する。このパスは、v
の所属する平衡二分木の根からu
に向けて(根の方向に)生えているとみなす- Normal Pathは片方向にリンクしている。つまり、
(vの所属するSplay Treeの根)->p == u
であるがu
はv
に関する情報を持たない
- Normal Pathは片方向にリンクしている。つまり、
expose
-
以上に説明した方法で木を管理したとき、内部でのポインタ操作によってPreferred EdgeとNormal Edgeを入れ替えることが出来る
-
splay(u), u->r = nullptr
$\leftrightarrow$u
から子に生えるPreferred EdgeをNormal Edgeに替える -
v
からu
にNormal Edgeが生えているとき、splay(u), u->r = v
$\leftrightarrow$u
から子に生えるPreferred EdgeをNormal Edgeに替える
-
-
上の二つを組み合わせると、Link Cut Treeの核である
expose(x)
:根からx
までのパスをPreferred Edgeからなるパスにする関数を実装できる
Ptr expose(Ptr t) {
Ptr rp = nullptr;
for(Ptr cur = t; cur; ) {
splay(cur);
// Preferred Edgeをxの所属する辺につなぎかえる
cur->r = rp;
// curの子に関する情報を更新する
update(cur);
// 次の親に渡すポインタをメモする
rp = cur;
// 次のPreferred Edgeに移動
cur = cur->p;
}
splay(t);
return rp;
}
その他の関数
-
expose
を利用すればlink/cut
をはじめとした様々な関数を実装できる -
evert(u)
: 頂点u
を木全体の根とする-
expose(u)
した後にtoggle(u)
するだけ- Preferred Edgeの最も根から遠い頂点に対して
splay(u), toggle(u)
をするとsplay Treeの順序関係が反転するためu
がPreferred Edgeの根側になる
- Preferred Edgeの最も根から遠い頂点に対して
-
-
link(u, v)
: 頂点u
と頂点v
をつなぐ(u, v
は連結成分でないことを仮定)-
u
の親をv
にすることを考える -
evert(u)
することでu
を木全体の根とする-
expose(u)
でも内部的にはu->p = nullptr
になるが、実際の木はu
の親が存在するため破綻する
-
-
expose(v)
する -
u->p = v
としてu
とv
をNormal Edgeでつなぐ-
evert(u)
またはexpose(u)
した頂点は親がnullptr
になっていることを利用
-
- 疑問点: linkするだけなら
expose(v)
いらなくない?evert(u), u->p = v
でいい気がする-
Gifted Infantの実装は
evert(u), expose(v)
している -
kimiyukiさんの提出だと
expose(v)
の行に// for the time complexity
と書いてある -
Library Checkerのfastestだと
expose(v)
していない - 以上をまとめると「あっても無くても良さそう?計算量解析に踏み入らないと理由はわからなそう?」という結論に
- パス加算クエリや部分木クエリに対応するときは
expose(v)
は必要そう(根方向からの伝播を反映させる必要があるため)
-
Gifted Infantの実装は
-
-
cut(u, v)
:頂点u
,v
を切り離す-
evert(u), expose(v)
する - この時
v->l == u
になっている -
v->l = u->p = nullptr
とすれば辺が切れる
-
- 実装の際は評価更新用の関数
update()
と評価伝播用の関数push()
を適切に入れる必要がある- 子の辺を張り変える前は
push()
で伝播させる - 子の辺を張り変えた後は
update()
で新しい子の情報を更新する
- 子の辺を張り変える前は
使い方
-
expose(u)
:根からu
までのパスをPreferred Edgeからなるパスにする -
evert(u)
: 頂点u
を木全体の根とする -
link(u, v)
: 頂点u
と頂点v
をつなぐ(u, v
は連結成分でないことを仮定) -
cut(u, v)
:頂点u
,v
を切り離す -
get_root(u)
:u
の所属する木の根を返す -
set_key(u, key)
:頂点u
のkey
を書き換える -
get_key(u, key)
:頂点u
のkey
の値を得る -
fold(u, v)
:u, v
間のパスのkey
の和を得る
Required by
- 遅延伝搬Link/Cut Tree (lct/link-cut-tree-lazy.hpp)
- 部分木クエリLink/Cut Tree (lct/link-cut-tree-subtree.hpp)
- Link/Cut Tree (lct/link-cut-tree.hpp)
Verified with
- verify/verify-yosupo-ds/yosupo-dynamic-tree-vertex-add-path-sum.test.cpp
- verify/verify-yosupo-ds/yosupo-dynamic-tree-vertex-add-subtree-sum.test.cpp
- verify/verify-yosupo-ds/yosupo-dynamic-tree-vertex-set-path-composite.test.cpp
- verify/verify-yosupo-ds/yosupo-offline-dynamic-connectivity.test.cpp
- verify/verify-yosupo-ds/yosupo-range-add-range-sum-linkcuttree.test.cpp