Link/Cut Tree
(lct/link-cut-tree.hpp)
- View this file on GitHub
- Last update: 2023-08-10 18:41:15+09:00
- Include:
#include "lct/link-cut-tree.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
の和を得る
Depends on
Link/Cut Tree(base) (lct/link-cut-base.hpp)
反転可能平衡二分木(基底クラス) (lct/reversible-bbst-base.hpp)
Splay Tree(base) (lct/splay-base.hpp)
反転可能Splay Tree (lct/splay-reversible.hpp)
Verified with
verify/verify-yosupo-ds/yosupo-dynamic-tree-vertex-add-path-sum.test.cpp
verify/verify-yosupo-ds/yosupo-dynamic-tree-vertex-set-path-composite.test.cpp
Code
#pragma once
#include "splay-reversible.hpp"
//
#include "link-cut-base.hpp"
template <typename T, T (*f)(T, T), T (*ts)(T)>
struct LinkCutTree : LinkCutBase<ReversibleSplayTree<T, f, ts>> {};
/**
* @brief Link/Cut Tree
* @docs docs/lct/link-cut-tree.md
*/
#line 2 "lct/link-cut-tree.hpp"
#line 2 "lct/splay-reversible.hpp"
#line 2 "lct/reversible-bbst-base.hpp"
template <typename Tree, typename Node, typename T, T (*f)(T, T), T (*ts)(T)>
struct ReversibleBBST : Tree {
using Tree::merge;
using Tree::split;
using typename Tree::Ptr;
ReversibleBBST() = default;
virtual void toggle(Ptr t) {
if(!t) return;
swap(t->l, t->r);
t->sum = ts(t->sum);
t->rev ^= true;
}
T fold(Ptr &t, int a, int b) {
auto x = split(t, a);
auto y = split(x.second, b - a);
auto ret = sum(y.first);
t = merge(x.first, merge(y.first, y.second));
return ret;
}
void reverse(Ptr &t, int a, int b) {
auto x = split(t, a);
auto y = split(x.second, b - a);
toggle(y.first);
t = merge(x.first, merge(y.first, y.second));
}
Ptr update(Ptr t) override {
if (!t) return t;
t->cnt = 1;
t->sum = t->key;
if (t->l) t->cnt += t->l->cnt, t->sum = f(t->l->sum, t->sum);
if (t->r) t->cnt += t->r->cnt, t->sum = f(t->sum, t->r->sum);
return t;
}
protected:
inline T sum(const Ptr t) { return t ? t->sum : T(); }
void push(Ptr t) override {
if (!t) return;
if (t->rev) {
if (t->l) toggle(t->l);
if (t->r) toggle(t->r);
t->rev = false;
}
}
};
/**
* @brief 反転可能平衡二分木(基底クラス)
*/
#line 2 "lct/splay-base.hpp"
template <typename Node>
struct SplayTreeBase {
using Ptr = Node *;
template <typename... Args>
Ptr my_new(const Args &...args) {
return new Node(args...);
}
void my_del(Ptr p) { delete p; }
bool is_root(Ptr t) { return !(t->p) || (t->p->l != t && t->p->r != t); }
int size(Ptr t) const { return count(t); }
virtual void splay(Ptr t) {
if (!t) return;
push(t);
while (!is_root(t)) {
Ptr q = t->p;
if (is_root(q)) {
push(q), push(t);
rot(t);
} else {
Ptr r = q->p;
push(r), push(q), push(t);
if (pos(q) == pos(t))
rot(q), rot(t);
else
rot(t), rot(t);
}
}
}
Ptr get_left(Ptr t) {
while (t->l) push(t), t = t->l;
return t;
}
Ptr get_right(Ptr t) {
while (t->r) push(t), t = t->r;
return t;
}
pair<Ptr, Ptr> split(Ptr t, int k) {
if (!t) return {nullptr, nullptr};
if (k == 0) return {nullptr, t};
if (k == count(t)) return {t, nullptr};
push(t);
if (k <= count(t->l)) {
auto x = split(t->l, k);
t->l = x.second;
t->p = nullptr;
if (x.second) x.second->p = t;
return {x.first, update(t)};
} else {
auto x = split(t->r, k - count(t->l) - 1);
t->r = x.first;
t->p = nullptr;
if (x.first) x.first->p = t;
return {update(t), x.second};
}
}
Ptr merge(Ptr l, Ptr r) {
if (!l && !r) return nullptr;
if (!l) return splay(r), r;
if (!r) return splay(l), l;
splay(l), splay(r);
l = get_right(l);
splay(l);
l->r = r;
r->p = l;
update(l);
return l;
}
using Key = decltype(Node::key);
Ptr build(const vector<Key> &v) { return build(0, v.size(), v); }
Ptr build(int l, int r, const vector<Key> &v) {
if (l == r) return nullptr;
if (l + 1 == r) return my_new(v[l]);
return merge(build(l, (l + r) >> 1, v), build((l + r) >> 1, r, v));
}
template <typename... Args>
void insert(Ptr &t, int k, const Args &...args) {
splay(t);
auto x = split(t, k);
t = merge(merge(x.first, my_new(args...)), x.second);
}
void erase(Ptr &t, int k) {
splay(t);
auto x = split(t, k);
auto y = split(x.second, 1);
my_del(y.first);
t = merge(x.first, y.second);
}
virtual Ptr update(Ptr t) = 0;
protected:
inline int count(Ptr t) const { return t ? t->cnt : 0; }
virtual void push(Ptr t) = 0;
Ptr build(const vector<Ptr> &v) { return build(0, v.size(), v); }
Ptr build(int l, int r, const vector<Ptr> &v) {
if (l + 1 >= r) return v[l];
return merge(build(l, (l + r) >> 1, v), build((l + r) >> 1, r, v));
}
inline int pos(Ptr t) {
if (t->p) {
if (t->p->l == t) return -1;
if (t->p->r == t) return 1;
}
return 0;
}
virtual void rot(Ptr t) {
Ptr x = t->p, y = x->p;
if (pos(t) == -1) {
if ((x->l = t->r)) t->r->p = x;
t->r = x, x->p = t;
} else {
if ((x->r = t->l)) t->l->p = x;
t->l = x, x->p = t;
}
update(x), update(t);
if ((t->p = y)) {
if (y->l == x) y->l = t;
if (y->r == x) y->r = t;
}
}
};
/**
* @brief Splay Tree(base)
*/
#line 5 "lct/splay-reversible.hpp"
template <typename T>
struct ReversibleSplayTreeNode {
using Ptr = ReversibleSplayTreeNode *;
Ptr l, r, p;
T key, sum;
int cnt;
bool rev;
ReversibleSplayTreeNode(const T &t = T())
: l(), r(), p(), key(t), sum(t), cnt(1), rev(false) {}
};
template <typename T, T (*f)(T, T), T (*ts)(T)>
struct ReversibleSplayTree
: ReversibleBBST<SplayTreeBase<ReversibleSplayTreeNode<T>>,
ReversibleSplayTreeNode<T>, T, f, ts> {
using Node = ReversibleSplayTreeNode<T>;
};
/**
* @brief 反転可能Splay Tree
*/
#line 4 "lct/link-cut-tree.hpp"
//
#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
*/
#line 7 "lct/link-cut-tree.hpp"
template <typename T, T (*f)(T, T), T (*ts)(T)>
struct LinkCutTree : LinkCutBase<ReversibleSplayTree<T, f, ts>> {};
/**
* @brief Link/Cut Tree
* @docs docs/lct/link-cut-tree.md
*/