競技プログラミング用 C++ ライブラリ
components/persistent_rope.hpp · GitHub で見る
共通ページ
重み付きの断片列を、節点を共有する永続 AVL 木で管理する内部部品です。永続 deque の更新や、動的パターン照合 の部分文字列の分割・連結・コピーに使います。
#include "lib/components/persistent_rope.hpp"
#include <string>
std::uint64_t length(const std::string &s) { return s.size(); }
int main() {
PersistentRope<std::string, length> rope;
int original = rope.build(std::vector<std::string>{"ab", "cde"});
auto [left, right] = rope.split(original, 3, [](const std::string &s, std::uint64_t p) {
return std::pair{s.substr(0, p), s.substr(p)};
});
int changed = rope.join(right, left);
std::string text;
rope.for_each(changed, [&](const std::string &s) { text += s; });
assert(text == "deabc" && rope.size(original) == 5);
auto [chunk, offset] = rope.at(original, 2);
assert(chunk == "cde" && offset == 0);
PersistentRope<std::string, length> compact;
int copied = compact.copy_from(rope, changed);
assert(compact.size(copied) == 5);
}
木の根は int の節点番号で持ちます。0 は空の列です。木自身は現在の根を保持しないため、利用側で根番号を保存します。make・build・join・split などは新しい根を返し、以前の根の内容を変更しません。根番号を再利用して同じ列を何度も連結できます。
T はデフォルト構築・コピー・代入できる型にし、weight(value) は断片の正の整数の重みを返す関数にします。重みと列全体の重みは uint64_t に収めてください。位置は断片の個数ではなく、先頭からの重みの合計で指定します。
split(root,p,splitter) は重みの合計が p となる位置で分割します。0 <= p <= size(root) が必要です。断片の途中で分割するときだけ splitter(value,offset) を呼びます。返す pair<T,T> の重みは、それぞれ offset と weight(value)-offset にしてください。断片の境界では呼びません。at(root,p) は、その位置を含む断片と、断片内の重みのオフセットを返します。
insert は 0 <= p <= size(root)、erase は 0 <= l <= r <= size(root) とします。どちらも断片の途中を操作するときだけ、split と同じ条件の splitter を呼びます。replace は p < size(root) が必要で、位置を含む断片全体を置き換えます。新しい値の重みは、その断片の重みと同じにしてください。
根番号は、その節点を保持する同じインスタンスで使います。別のインスタンスへ移す場合は copy_from を使います。不要になった節点は自動で回収せず、build でも既存の領域は残ります。新しいインスタンスへ copy_from すると、指定した根から到達する節点だけを、共有関係を保って移せます。保持する節点数は INT_MAX 未満にしてください。
\(L\) を根からたどった列の断片数、\(A\) を保持する節点数、\(C\) を分割コールバックの時間とします。表は T のコピー・構築、weight、訪問コールバックが定数時間の場合です。文字列などを T にする場合は、そのコピー時間も加わります。† は節点配列の伸長を含む償却計算量です。
| 操作 | 内容・返り値 | 計算量 |
|---|---|---|
PersistentRope<T,weight>() |
空の節点領域を作る。空列の根は 0
|
\(O(1)\) |
make(value) |
断片 1 つから葉を作り、根番号を返す | † \(O(1)\) |
build(values) |
vector<T> の断片列から木を作り、根番号を返す |
† \(O(L)\) |
join(a,b) |
2 つの根の列をこの順に連結した根番号 | † \(O(\log L)\) |
split(root,p,splitter) |
分割後の左・右の根番号の組 | † \(O(\log L+C)\) |
replace(root,p,value) |
位置 p を含む断片を、同じ重みの value に置き換えた根番号 |
† \(O(\log L)\) |
insert(root,p,value,splitter) |
位置 p に断片を挿入した根番号 |
† \(O(\log L+C)\) |
erase(root,l,r,splitter) |
重みの区間 [l,r) を削除した根番号 |
† \(O(\log L+C)\) |
size(root) |
列の重みの合計。根 0 なら 0 |
\(O(1)\) |
storage_nodes() |
このインスタンスが保持する節点数 | \(O(1)\) |
leaf(node) |
葉か。leaf(0) は false |
\(O(1)\) |
value(node) |
葉が持つ断片をコピーして返す | \(O(1)\) |
front(root) / back(root)
|
先頭 / 末尾の断片をコピーして返す | \(O(\log L)\) |
pop_front(root) |
先頭の断片と、残りの列の根番号の組 | † \(O(\log L)\) |
pop_back(root) |
残りの列の根番号と、末尾の断片の組 | † \(O(\log L)\) |
at(root,p) |
位置を含む断片と、その断片内のオフセットの組 | \(O(\log L)\) |
for_each(root,visitor) |
左から各断片に visitor(value) を呼ぶ |
\(O(L)\) |
copy_from(other,root) |
別の領域から共有を保ってコピーし、コピー先の根を返す | † \(O(A)\) 時間・追加空間。ここではコピー元の保持数 |
| コピー構築・コピー代入 | 節点領域全体をコピー。同じ根番号をコピー先でも使える | \(O(A)\)。代入は旧領域の解放も加わる |
| ムーブ構築・ムーブ代入 | 節点領域を移動 | 構築 \(O(1)\)。代入は旧領域の解放も加わる |
value は葉にだけ、front・back・pop_front・pop_back は空でない根にだけ使います。at は p < size(root) が必要です。copy_from のコピー元は別のインスタンスにしてください。ムーブ後の根は移動先で使います。
共有している断片も for_each では列中に現れる回数だけ訪問します。断片の共有を繰り返すと、列の断片数は保持節点数よりはるかに大きくなり得ます。保持空間は \(O(A)\)、連結・分割などで追加する節点数は \(O(\log L)\) です。対数の項は断片数が 0・1 の場合に定数として扱います。
#ifndef PERSISTENT_ROPE_HPP
#define PERSISTENT_ROPE_HPP
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <limits>
#include <utility>
#include <vector>
template<class T, auto weight> class PersistentRope {
struct Node {
std::uint64_t size;
int left, right, height;
T value;
};
std::vector<Node> nodes_{{0, 0, 0, 0, T{}}};
int height(int v) const { return nodes_[v].height; }
int append(Node node) {
assert(nodes_.size() < std::size_t(std::numeric_limits<int>::max()));
nodes_.push_back(std::move(node));
return static_cast<int>(nodes_.size()) - 1;
}
int branch(int a, int b) {
if (!a) return b;
if (!b) return a;
assert(size(a) <= std::numeric_limits<std::uint64_t>::max() - size(b));
return append({size(a) + size(b), a, b, std::max(height(a), height(b)) + 1, T{}});
}
int balance(int a, int b) {
if (height(a) > height(b) + 1) {
int left = nodes_[a].left, right = nodes_[a].right;
if (height(left) >= height(right)) return branch(left, branch(right, b));
int middle_left = nodes_[right].left, middle_right = nodes_[right].right;
return branch(branch(left, middle_left), branch(middle_right, b));
}
if (height(b) > height(a) + 1) {
int left = nodes_[b].left, right = nodes_[b].right;
if (height(right) >= height(left)) return branch(branch(a, left), right);
int middle_left = nodes_[left].left, middle_right = nodes_[left].right;
return branch(branch(a, middle_left), branch(middle_right, right));
}
return branch(a, b);
}
public:
std::uint64_t size(int v) const { return nodes_[v].size; }
std::size_t storage_nodes() const { return nodes_.size() - 1; }
bool leaf(int v) const { return v && !nodes_[v].left; }
T value(int v) const { assert(leaf(v)); return nodes_[v].value; }
int make(T value) {
auto length = std::uint64_t(weight(value));
assert(length);
return append({length, 0, 0, 1, std::move(value)});
}
int join(int a, int b) {
if (!a || !b) return a ? a : b;
if (height(a) > height(b) + 1) {
int left = nodes_[a].left, right = nodes_[a].right;
return balance(left, join(right, b));
}
if (height(b) > height(a) + 1) {
int left = nodes_[b].left, right = nodes_[b].right;
return balance(join(a, left), right);
}
return branch(a, b);
}
int build(const std::vector<T> &values) {
auto visit = [&](auto &&self, std::size_t l, std::size_t r) -> int {
if (l == r) return 0;
if (r - l == 1) return make(values[l]);
auto m = l + (r - l) / 2;
int a = self(self, l, m), b = self(self, m, r);
return branch(a, b);
};
return visit(visit, 0, values.size());
}
template<class Splitter> std::pair<int, int> split(int v, std::uint64_t p, const Splitter &splitter) {
assert(p <= size(v));
if (!p) return {0, v};
if (p == size(v)) return {v, 0};
int left = nodes_[v].left, right = nodes_[v].right;
if (!left) {
T value = nodes_[v].value;
auto [a, b] = splitter(value, p);
int l = make(std::move(a)), r = make(std::move(b));
return {l, r};
}
auto k = size(left);
if (p == k) return {left, right};
if (p < k) {
auto [a, b] = split(left, p, splitter);
return {a, join(b, right)};
}
auto [a, b] = split(right, p - k, splitter);
return {join(left, a), b};
}
int replace(int v, std::uint64_t p, T value) {
assert(p < size(v));
int a = nodes_[v].left, b = nodes_[v].right;
if (!a) { assert(std::uint64_t(weight(value)) == size(v)); return make(std::move(value)); }
if (p < size(a)) a = replace(a, p, std::move(value));
else b = replace(b, p - size(a), std::move(value));
return branch(a, b);
}
template<class Splitter> int insert(int v, std::uint64_t p, T value, const Splitter &splitter) {
assert(p <= size(v));
if (!v) return make(std::move(value));
int a = nodes_[v].left, b = nodes_[v].right;
if (!a) {
if (!p) return branch(make(std::move(value)), v);
if (p == size(v)) return branch(v, make(std::move(value)));
auto [left, right] = split(v, p, splitter);
int middle = make(std::move(value));
return join(join(left, middle), right);
}
if (p <= size(a)) a = insert(a, p, std::move(value), splitter);
else b = insert(b, p - size(a), std::move(value), splitter);
return balance(a, b);
}
template<class Splitter> int erase(int v, std::uint64_t l, std::uint64_t r, const Splitter &splitter) {
assert(l <= r && r <= size(v));
if (l == r) return v;
if (!l && r == size(v)) return 0;
int a = nodes_[v].left, b = nodes_[v].right;
if (!a) {
if (!l) return split(v, r, splitter).second;
if (r == size(v)) return split(v, l, splitter).first;
auto [left, rest] = split(v, l, splitter);
int right = split(rest, r - l, splitter).second;
return join(left, right);
}
auto k = size(a);
if (r <= k) a = erase(a, l, r, splitter);
else if (l >= k) b = erase(b, l - k, r - k, splitter);
else {
a = erase(a, l, k, splitter);
b = erase(b, 0, r - k, splitter);
}
return join(a, b);
}
T front(int v) const { assert(v); while (nodes_[v].left) v = nodes_[v].left; return value(v); }
T back(int v) const { assert(v); while (nodes_[v].right) v = nodes_[v].right; return value(v); }
std::pair<T, int> pop_front(int v) {
assert(v);
int left = nodes_[v].left, right = nodes_[v].right;
if (!left) return {value(v), 0};
auto [value, rest] = pop_front(left);
return {std::move(value), join(rest, right)};
}
std::pair<int, T> pop_back(int v) {
assert(v);
int left = nodes_[v].left, right = nodes_[v].right;
if (!right) return {0, value(v)};
auto [rest, value] = pop_back(right);
return {join(left, rest), std::move(value)};
}
std::pair<T, std::uint64_t> at(int v, std::uint64_t p) const {
assert(p < size(v));
while (int left = nodes_[v].left) {
int right = nodes_[v].right;
auto length = size(left);
if (p < length) v = left;
else p -= length, v = right;
}
return {value(v), p};
}
template<class Visitor> void for_each(int v, const Visitor &visitor) const {
if (!v) return;
if (leaf(v)) { visitor(value(v)); return; }
for_each(nodes_[v].left, visitor);
for_each(nodes_[v].right, visitor);
}
int copy_from(const PersistentRope &other, int root) {
assert(this != &other);
std::vector<int> mapped(other.nodes_.size(), 0);
auto visit = [&](auto &&self, int v) -> int {
if (!v || mapped[v]) return mapped[v];
auto x = other.nodes_[v];
if (x.left) {
x.left = self(self, x.left);
x.right = self(self, x.right);
}
return mapped[v] = append(std::move(x));
};
return visit(visit, root);
}
};
#endif
#line 1 "components/persistent_rope.hpp"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <limits>
#include <utility>
#include <vector>
template<class T, auto weight> class PersistentRope {
struct Node {
std::uint64_t size;
int left, right, height;
T value;
};
std::vector<Node> nodes_{{0, 0, 0, 0, T{}}};
int height(int v) const { return nodes_[v].height; }
int append(Node node) {
assert(nodes_.size() < std::size_t(std::numeric_limits<int>::max()));
nodes_.push_back(std::move(node));
return static_cast<int>(nodes_.size()) - 1;
}
int branch(int a, int b) {
if (!a) return b;
if (!b) return a;
assert(size(a) <= std::numeric_limits<std::uint64_t>::max() - size(b));
return append({size(a) + size(b), a, b, std::max(height(a), height(b)) + 1, T{}});
}
int balance(int a, int b) {
if (height(a) > height(b) + 1) {
int left = nodes_[a].left, right = nodes_[a].right;
if (height(left) >= height(right)) return branch(left, branch(right, b));
int middle_left = nodes_[right].left, middle_right = nodes_[right].right;
return branch(branch(left, middle_left), branch(middle_right, b));
}
if (height(b) > height(a) + 1) {
int left = nodes_[b].left, right = nodes_[b].right;
if (height(right) >= height(left)) return branch(branch(a, left), right);
int middle_left = nodes_[left].left, middle_right = nodes_[left].right;
return branch(branch(a, middle_left), branch(middle_right, right));
}
return branch(a, b);
}
public:
std::uint64_t size(int v) const { return nodes_[v].size; }
std::size_t storage_nodes() const { return nodes_.size() - 1; }
bool leaf(int v) const { return v && !nodes_[v].left; }
T value(int v) const { assert(leaf(v)); return nodes_[v].value; }
int make(T value) {
auto length = std::uint64_t(weight(value));
assert(length);
return append({length, 0, 0, 1, std::move(value)});
}
int join(int a, int b) {
if (!a || !b) return a ? a : b;
if (height(a) > height(b) + 1) {
int left = nodes_[a].left, right = nodes_[a].right;
return balance(left, join(right, b));
}
if (height(b) > height(a) + 1) {
int left = nodes_[b].left, right = nodes_[b].right;
return balance(join(a, left), right);
}
return branch(a, b);
}
int build(const std::vector<T> &values) {
auto visit = [&](auto &&self, std::size_t l, std::size_t r) -> int {
if (l == r) return 0;
if (r - l == 1) return make(values[l]);
auto m = l + (r - l) / 2;
int a = self(self, l, m), b = self(self, m, r);
return branch(a, b);
};
return visit(visit, 0, values.size());
}
template<class Splitter> std::pair<int, int> split(int v, std::uint64_t p, const Splitter &splitter) {
assert(p <= size(v));
if (!p) return {0, v};
if (p == size(v)) return {v, 0};
int left = nodes_[v].left, right = nodes_[v].right;
if (!left) {
T value = nodes_[v].value;
auto [a, b] = splitter(value, p);
int l = make(std::move(a)), r = make(std::move(b));
return {l, r};
}
auto k = size(left);
if (p == k) return {left, right};
if (p < k) {
auto [a, b] = split(left, p, splitter);
return {a, join(b, right)};
}
auto [a, b] = split(right, p - k, splitter);
return {join(left, a), b};
}
int replace(int v, std::uint64_t p, T value) {
assert(p < size(v));
int a = nodes_[v].left, b = nodes_[v].right;
if (!a) { assert(std::uint64_t(weight(value)) == size(v)); return make(std::move(value)); }
if (p < size(a)) a = replace(a, p, std::move(value));
else b = replace(b, p - size(a), std::move(value));
return branch(a, b);
}
template<class Splitter> int insert(int v, std::uint64_t p, T value, const Splitter &splitter) {
assert(p <= size(v));
if (!v) return make(std::move(value));
int a = nodes_[v].left, b = nodes_[v].right;
if (!a) {
if (!p) return branch(make(std::move(value)), v);
if (p == size(v)) return branch(v, make(std::move(value)));
auto [left, right] = split(v, p, splitter);
int middle = make(std::move(value));
return join(join(left, middle), right);
}
if (p <= size(a)) a = insert(a, p, std::move(value), splitter);
else b = insert(b, p - size(a), std::move(value), splitter);
return balance(a, b);
}
template<class Splitter> int erase(int v, std::uint64_t l, std::uint64_t r, const Splitter &splitter) {
assert(l <= r && r <= size(v));
if (l == r) return v;
if (!l && r == size(v)) return 0;
int a = nodes_[v].left, b = nodes_[v].right;
if (!a) {
if (!l) return split(v, r, splitter).second;
if (r == size(v)) return split(v, l, splitter).first;
auto [left, rest] = split(v, l, splitter);
int right = split(rest, r - l, splitter).second;
return join(left, right);
}
auto k = size(a);
if (r <= k) a = erase(a, l, r, splitter);
else if (l >= k) b = erase(b, l - k, r - k, splitter);
else {
a = erase(a, l, k, splitter);
b = erase(b, 0, r - k, splitter);
}
return join(a, b);
}
T front(int v) const { assert(v); while (nodes_[v].left) v = nodes_[v].left; return value(v); }
T back(int v) const { assert(v); while (nodes_[v].right) v = nodes_[v].right; return value(v); }
std::pair<T, int> pop_front(int v) {
assert(v);
int left = nodes_[v].left, right = nodes_[v].right;
if (!left) return {value(v), 0};
auto [value, rest] = pop_front(left);
return {std::move(value), join(rest, right)};
}
std::pair<int, T> pop_back(int v) {
assert(v);
int left = nodes_[v].left, right = nodes_[v].right;
if (!right) return {0, value(v)};
auto [rest, value] = pop_back(right);
return {join(left, rest), std::move(value)};
}
std::pair<T, std::uint64_t> at(int v, std::uint64_t p) const {
assert(p < size(v));
while (int left = nodes_[v].left) {
int right = nodes_[v].right;
auto length = size(left);
if (p < length) v = left;
else p -= length, v = right;
}
return {value(v), p};
}
template<class Visitor> void for_each(int v, const Visitor &visitor) const {
if (!v) return;
if (leaf(v)) { visitor(value(v)); return; }
for_each(nodes_[v].left, visitor);
for_each(nodes_[v].right, visitor);
}
int copy_from(const PersistentRope &other, int root) {
assert(this != &other);
std::vector<int> mapped(other.nodes_.size(), 0);
auto visit = [&](auto &&self, int v) -> int {
if (!v || mapped[v]) return mapped[v];
auto x = other.nodes_[v];
if (x.left) {
x.left = self(self, x.left);
x.right = self(self, x.right);
}
return mapped[v] = append(std::move(x));
};
return visit(visit, root);
}
};