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
Code
#pragma once
template <typename Splay>
struct LinkCutBase : Splay {
using Node = typename Splay::Node;
using Ptr = Node*;
virtual Ptr expose(Ptr t) {
Ptr rp = nullptr;
for (Ptr cur = t; cur; cur = cur->p) {
this->splay(cur);
cur->r = rp;
this->update(cur);
rp = cur;
}
this->splay(t);
return rp;
}
virtual void link(Ptr u, Ptr v) {
evert(u);
expose(v);
u->p = v;
}
void cut(Ptr u, Ptr v) {
evert(u);
expose(v);
assert(u->p == v);
v->l = u->p = nullptr;
this->update(v);
}
void evert(Ptr t) {
expose(t);
this->toggle(t);
this->push(t);
}
Ptr lca(Ptr u, Ptr v) {
if (get_root(u) != get_root(v)) return nullptr;
expose(u);
return expose(v);
}
Ptr get_kth(Ptr x, int k) {
expose(x);
while (x) {
this->push(x);
if (x->r && x->r->sz > k) {
x = x->r;
} else {
if (x->r) k -= x->r->sz;
if (k == 0) return x;
k -= 1;
x = x->l;
}
}
return nullptr;
}
Ptr get_root(Ptr x) {
expose(x);
while (x->l) this->push(x), x = x->l;
return x;
}
Ptr get_parent(Ptr x) {
expose(x);
Ptr p = x->l;
if(p == nullptr) return nullptr;
while (true) {
this->push(p);
if (p->r == nullptr) return p;
p = p->r;
}
exit(1);
}
virtual void set_key(Ptr t, const decltype(Node::key)& key) {
this->splay(t);
t->key = key;
this->update(t);
}
virtual decltype(Node::key) get_key(Ptr t) { return t->key; }
decltype(Node::key) fold(Ptr u, Ptr v) {
evert(u);
expose(v);
return v->sum;
}
};
/**
* @brief Link/Cut Tree(base)
* @docs docs/lct/link-cut-tree.md
*/#line 2 "lct/link-cut-base.hpp"
template <typename Splay>
struct LinkCutBase : Splay {
using Node = typename Splay::Node;
using Ptr = Node*;
virtual Ptr expose(Ptr t) {
Ptr rp = nullptr;
for (Ptr cur = t; cur; cur = cur->p) {
this->splay(cur);
cur->r = rp;
this->update(cur);
rp = cur;
}
this->splay(t);
return rp;
}
virtual void link(Ptr u, Ptr v) {
evert(u);
expose(v);
u->p = v;
}
void cut(Ptr u, Ptr v) {
evert(u);
expose(v);
assert(u->p == v);
v->l = u->p = nullptr;
this->update(v);
}
void evert(Ptr t) {
expose(t);
this->toggle(t);
this->push(t);
}
Ptr lca(Ptr u, Ptr v) {
if (get_root(u) != get_root(v)) return nullptr;
expose(u);
return expose(v);
}
Ptr get_kth(Ptr x, int k) {
expose(x);
while (x) {
this->push(x);
if (x->r && x->r->sz > k) {
x = x->r;
} else {
if (x->r) k -= x->r->sz;
if (k == 0) return x;
k -= 1;
x = x->l;
}
}
return nullptr;
}
Ptr get_root(Ptr x) {
expose(x);
while (x->l) this->push(x), x = x->l;
return x;
}
Ptr get_parent(Ptr x) {
expose(x);
Ptr p = x->l;
if(p == nullptr) return nullptr;
while (true) {
this->push(p);
if (p->r == nullptr) return p;
p = p->r;
}
exit(1);
}
virtual void set_key(Ptr t, const decltype(Node::key)& key) {
this->splay(t);
t->key = key;
this->update(t);
}
virtual decltype(Node::key) get_key(Ptr t) { return t->key; }
decltype(Node::key) fold(Ptr u, Ptr v) {
evert(u);
expose(v);
return v->sum;
}
};
/**
* @brief Link/Cut Tree(base)
* @docs docs/lct/link-cut-tree.md
*/