競技プログラミング用 C++ ライブラリ
string/basic_substring_structure.hpp · GitHub で見る
共通ページ
固定した文字列の異なる非空部分文字列を、階段状のブロックのセルへ一意に配置します。同じブロックの部分文字列は出現回数が等しく、先頭・末尾の 1 バイト削除をセル間の移動で扱えます。全セルを展開せず、ブロックと行・列の情報を線形空間で保持します。
#include "lib/string/basic_substring_structure.hpp"
#include <cassert>
int main() {
BasicSubstringStructure structure("banana");
auto first = structure.locate(1, 4);
auto second = structure.locate(3, 6);
assert(first && second && *first == *second);
assert(structure.occurrence_count(*first) == 2);
assert(structure.distinct_substrings() == 15);
auto [l, r] = structure.substring_range(*first);
assert(structure.text().substr(l, r - l) == "ana");
assert(structure.erase_first(*first) == structure.locate(2, 4));
assert(structure.erase_last(*first) == structure.locate(1, 3));
assert(!structure.erase_first(*structure.locate(1, 2)));
assert(!structure.locate(2, 2));
}
入力は std::string_view で渡し、内部にコピーします。構築後に入力を変更・破棄しても使えます。NUL を含む全 256 バイトに対応し、位置と長さはバイト単位です。本文長を \(n\) として n < INT_MAX/2 を満たす必要があります。
locate(l,r) は本文の半開区間 [l,r) を表すセルを返します。異なる位置に同じ部分文字列が現れる場合も、返すセルは同じです。空文字列はセルに含めず、l == r なら std::nullopt です。本文外の文字列を検索する API はありません。
Position は int row と int column を持ち、等値比較ができます。行と列は全ブロックを通した番号です。shape() が返す (H,W) に対して行番号は [0,H)、列番号は [0,W) ですが、この長方形の全セルが有効とは限りません。valid(position) で判定できます。
Block は次の int フィールドを持ちます。
| フィールド | 意味 |
|---|---|
left, right
|
このブロックの代表文字列を取り出す本文上の半開区間 [left,right)
|
row_begin, row_end
|
ブロックの行番号の半開区間 |
column_begin, column_end
|
ブロックの列番号の半開区間 |
occurrences |
ブロック内の各部分文字列の出現回数。重なる出現も数える |
ブロック b 内のセル (row_begin + dx, column_begin + dy) は、本文の [left + dy, right - dx) に対応します。右へ進むと先頭を削除し、下へ進むと末尾を削除します。各行の有効なセルは左端から width(row) 個、各列では上端から height(column) 個です。行の幅と列の高さは、ブロック内で番号が増えるにつれて非増加になります。
例えば本文が abab のとき、代表文字列 ab、出現回数 2 のブロックは次の形です。右下は無効なセルです。
| 局所行・列 | dy = 0 |
dy = 1 |
|---|---|---|
dx = 0 |
ab |
b |
dx = 1 |
a |
— |
このブロックでは幅も高さも [2,1] です。残る abab・bab・aba・ba は出現回数 1 のブロックに入ります。同じ出現回数でも、必ず同じブロックになるわけではありません。
ブロック番号は [0,block_count()) です。出現回数の降順に並び、同じ回数のブロック同士の順序は未規定です。非空の本文では最後のブロックの代表が本文全体となり、出現回数 1 の部分文字列をすべて含みます。代表区間は出現位置の一つであり、非空区間の substring_range(*locate(l,r)) が元の (l,r) と一致するとは限りません。
right(row) は、その行の左端の文字列から width(row) バイトを先頭から削除した後の行番号です。返り値が t >= 0 なら、移動先のセルは {t, block(row_block(t)).column_begin} です。down(column) は、その列の上端の文字列から height(column) バイトを末尾から削除した後の列番号で、移動先は {block(column_block(t)).row_begin, t} です。削除後が空なら、どちらも -1 を返します。
これらは行・列の端から別のブロックへ移るためのリンクです。移動先では出現回数が増え、ブロック番号が小さくなります。任意のセルから 1 バイトだけ削除する場合は erase_first・erase_last を使います。ブロック内なら隣接セルへ移り、端ならリンク先へ移ります。
| 操作 | 内容・返り値 |
|---|---|
BasicSubstringStructure() |
空の本文で構築 |
BasicSubstringStructure(text) |
std::string_view の本文で構築 |
build(text) / clear()
|
本文を置き換えて再構築 / 空にする。返り値は void
|
size() / empty() / text()
|
本文長を int / 空判定を bool / 保存した本文を std::string_view で返す |
block_count() / shape()
|
ブロック数を int / 総行数・総列数を std::pair<int,int> で返す |
blocks() / block(b)
|
全ブロックを std::span<const Block> / 番号 b のブロックを const Block& で返す |
row_block(row) / column_block(column)
|
行 / 列が属するブロックの番号を int で返す |
width(row) / height(column)
|
行の幅 / 列の高さを int で返す |
right(row) / down(column)
|
別ブロックへの行 / 列のリンクを int で返す。空文字列へのリンクは -1
|
valid(position) |
有効なセルかを bool で返す。任意の int 座標を渡せる |
locate(l,r) |
本文の [l,r) に対応する std::optional<Position>。空区間なら std::nullopt
|
substring_range(position) |
セルの文字列を取り出せる本文の半開区間を std::pair<int,int> で返す |
occurrence_count(position) |
セルの文字列の出現回数を int で返す |
erase_first(position) / erase_last(position)
|
先頭 / 末尾を 1 バイト削除したセルを std::optional<Position> で返す。元が 1 バイトなら std::nullopt
|
distinct_substrings() |
異なる非空部分文字列の数を std::uint64_t で返す |
swap(other) |
2 つの構造の内容を交換。返り値は void
|
添字引数は int です。locate には 0 <= l <= r <= size()、ブロック・行・列の取得には各範囲内の番号を渡します。substring_range・occurrence_count・erase_first・erase_last には有効な Position が必要です。valid 以外では無効な番号を扱いません。検索・取得は const なオブジェクトから呼べます。
空の本文ではブロック・行・列・異なる非空部分文字列の数がすべて 0 です。blocks() は空、shape() は (0,0)、locate(0,0) は std::nullopt になります。
build・clear・代入後は、それ以前のブロック番号・セル・参照・span を使わないでください。build(structure.text()) のように保存した本文を渡して再構築することもできます。コピーしたオブジェクトは独立して使えます。ムーブ後の移動元は空になります。
行の幅を合計すると、異なる部分文字列の数になります。さらに各行が属するブロックの出現回数を掛ければ、本文中の非空区間の総数を得られます。次の例は全セルを展開せず、行の情報だけを走査します。
#include "lib/string/basic_substring_structure.hpp"
#include <cassert>
#include <cstdint>
int main() {
BasicSubstringStructure structure("abab");
std::uint64_t distinct = 0, intervals = 0;
for (const auto &b : structure.blocks()) {
for (int row = b.row_begin; row < b.row_end; ++row) {
auto cells = std::uint64_t(structure.width(row));
distinct += cells;
intervals += cells * b.occurrences;
}
}
assert(distinct == 7 && distinct == structure.distinct_substrings());
assert(intervals == 10);
}
参考: Basic Substring Structure に関する論文(arXiv:2312.11873)。順方向・逆方向の接尾辞木と、文字列長を重みとする祖先検索を使います。部分文字列の同一性判定にハッシュや乱数は使いません。
構築・build は \(O(n\log n)\) 時間、保持空間と構築時の追加空間は \(O(n)\) です。locate は \(O(\log n)\) 時間、それ以外の取得・判定・削除操作は \(O(1)\) 時間です。空入力と 1 バイトの入力では対数の項を定数として扱います。再構築・clear・代入では旧領域の解放も加わります。コピーは保持量に比例し、ムーブ構築は \(O(1)\) 時間です。
非空の本文では総行数・総列数はそれぞれ高々 \(2n-1\) で、ブロック数も \(O(n)\) です。上の行走査は \(O(n)\) 時間ですが、全有効セルを列挙すると異なる部分文字列数に比例し、最悪 \(O(n^2)\) 時間かかります。
#ifndef BASIC_SUBSTRING_STRUCTURE_HPP
#define BASIC_SUBSTRING_STRUCTURE_HPP
#include "../components/suffix_locus.hpp"
#include <cstdint>
#include <optional>
#include <span>
#include <string>
#include <utility>
#include <vector>
class BasicSubstringStructure {
public:
struct Position {
int row, column;
friend bool operator==(const Position &, const Position &) = default;
};
struct Block {
int left, right, row_begin, row_end, column_begin, column_end, occurrences;
};
private:
std::string text_;
SuffixLocusIndex index_;
std::vector<Block> blocks_;
std::vector<int> row_block_, column_block_, width_, height_, right_, down_, node_column_;
std::uint64_t distinct_ = 0;
void construct(std::string_view text) {
assert(text.size() < std::size_t(std::numeric_limits<int>::max() / 2));
text_ = text;
if (text_.empty()) return;
int n = size();
SuffixTree forward(text_), backward(std::string(text_.rbegin(), text_.rend()));
index_.build(forward);
SuffixLocusIndex reverse_index(backward);
int columns = forward.node_count() - 1, rows = backward.node_count() - 1;
std::vector<int> order{0}, low(columns + 1, 257), high(columns + 1, -1);
order.reserve(columns + 1);
for (std::size_t i = 0; i < order.size(); ++i) {
int v = order[i], p = forward.suffix_start(v);
if (p >= 0) low[v] = high[v] = p ? static_cast<unsigned char>(text_[p - 1]) + 1 : 0;
for (int u : forward.children(v)) order.push_back(u);
}
for (std::size_t i = order.size(); i-- > 1;) {
int v = order[i], p = forward.parent(v);
low[p] = std::min(low[p], low[v]);
high[p] = std::max(high[p], high[v]);
}
std::vector<int> heads, counts(n + 1);
for (int v = 1; v <= columns; ++v) {
if (low[v] != high[v] || low[v] == 0) {
heads.push_back(v);
++counts[forward.occurrence_count(v)];
}
}
int total = 0;
for (int count = n; count > 0; --count) {
int next = total + counts[count];
counts[count] = total;
total = next;
}
order.resize(heads.size());
for (int v : heads) order[counts[forward.occurrence_count(v)]++] = v;
blocks_.reserve(heads.size());
row_block_.reserve(rows); width_.reserve(rows); right_.reserve(rows);
column_block_.reserve(columns); height_.reserve(columns); down_.reserve(columns);
node_column_.assign(columns + 1, -1);
std::vector<int> node_row(rows + 1, -1);
auto suffix_link = [](const SuffixTree &tree, const SuffixLocusIndex &index, int v) {
int p = tree.suffix_start(v);
if (p >= 0) return tree.suffix_node(p + 1);
auto [l, r] = tree.label_range(v);
return index.locate(l + 1, r).node;
};
for (int v : order) {
auto [l, r] = forward.label_range(v);
int u = reverse_index.locate(n - r, n - l).node;
assert(backward.depth(u) == r - l);
int h = forward.depth(v) - forward.depth(forward.parent(v));
int w = backward.depth(u) - backward.depth(backward.parent(u));
int row = static_cast<int>(row_block_.size()), column = static_cast<int>(column_block_.size());
int id = block_count(), count = forward.occurrence_count(v);
blocks_.push_back({l, r, row, row + h, column, column + w, count});
for (int i = 0, a = u; i < h; ++i) {
assert(a > 0 && node_row[a] == -1 && backward.occurrence_count(a) == count);
node_row[a] = row + i;
row_block_.push_back(id);
width_.push_back(backward.depth(a) - backward.depth(backward.parent(a)));
right_.push_back(backward.parent(a));
distinct_ += width_.back();
if (i + 1 < h) a = suffix_link(backward, reverse_index, a);
}
for (int i = 0, a = v; i < w; ++i) {
assert(a > 0 && node_column_[a] == -1 && forward.occurrence_count(a) == count);
node_column_[a] = column + i;
column_block_.push_back(id);
height_.push_back(forward.depth(a) - forward.depth(forward.parent(a)));
down_.push_back(forward.parent(a));
if (i + 1 < w) a = suffix_link(forward, index_, a);
}
}
assert(row_block_.size() == std::size_t(rows) && column_block_.size() == std::size_t(columns));
for (int &v : right_) v = node_row[v];
for (int &v : down_) v = node_column_[v];
}
public:
BasicSubstringStructure() = default;
explicit BasicSubstringStructure(std::string_view text) { construct(text); }
BasicSubstringStructure(const BasicSubstringStructure &) = default;
BasicSubstringStructure &operator=(const BasicSubstringStructure &) = default;
BasicSubstringStructure(BasicSubstringStructure &&other) noexcept { swap(other); }
BasicSubstringStructure &operator=(BasicSubstringStructure &&other) noexcept {
if (this != &other) { BasicSubstringStructure moved(std::move(other)); swap(moved); }
return *this;
}
void swap(BasicSubstringStructure &other) noexcept {
text_.swap(other.text_);
index_.swap(other.index_);
blocks_.swap(other.blocks_);
row_block_.swap(other.row_block_); column_block_.swap(other.column_block_);
width_.swap(other.width_); height_.swap(other.height_);
right_.swap(other.right_); down_.swap(other.down_); node_column_.swap(other.node_column_);
std::swap(distinct_, other.distinct_);
}
void build(std::string_view text) { BasicSubstringStructure next(text); swap(next); }
void clear() { BasicSubstringStructure next; swap(next); }
int size() const { return static_cast<int>(text_.size()); }
bool empty() const { return text_.empty(); }
std::string_view text() const { return text_; }
int block_count() const { return static_cast<int>(blocks_.size()); }
std::span<const Block> blocks() const { return blocks_; }
const Block &block(int id) const { assert(0 <= id && id < block_count()); return blocks_[id]; }
std::pair<int, int> shape() const { return {static_cast<int>(row_block_.size()), static_cast<int>(column_block_.size())}; }
int row_block(int row) const { assert(0 <= row && row < shape().first); return row_block_[row]; }
int column_block(int column) const { assert(0 <= column && column < shape().second); return column_block_[column]; }
int width(int row) const { assert(0 <= row && row < shape().first); return width_[row]; }
int height(int column) const { assert(0 <= column && column < shape().second); return height_[column]; }
int right(int row) const { assert(0 <= row && row < shape().first); return right_[row]; }
int down(int column) const { assert(0 <= column && column < shape().second); return down_[column]; }
bool valid(Position p) const {
if (p.row < 0 || p.row >= shape().first || p.column < 0 || p.column >= shape().second) return false;
int id = row_block_[p.row];
return column_block_[p.column] == id && p.column - blocks_[id].column_begin < width_[p.row];
}
std::optional<Position> locate(int l, int r) const {
assert(0 <= l && l <= r && r <= size());
if (l == r) return std::nullopt;
int column = node_column_[index_.locate(l, r).node];
const Block &b = blocks_[column_block_[column]];
int row = b.row_begin + ((b.right - b.left) - (r - l) - (column - b.column_begin));
return Position{row, column};
}
std::pair<int, int> substring_range(Position p) const {
assert(valid(p));
const Block &b = blocks_[row_block_[p.row]];
return {b.left + (p.column - b.column_begin), b.right - (p.row - b.row_begin)};
}
int occurrence_count(Position p) const { assert(valid(p)); return blocks_[row_block_[p.row]].occurrences; }
std::optional<Position> erase_first(Position p) const {
assert(valid(p));
if (p.column + 1 < blocks_[row_block_[p.row]].column_begin + width_[p.row]) return Position{p.row, p.column + 1};
int row = right_[p.row];
if (row == -1) return std::nullopt;
return Position{row, blocks_[row_block_[row]].column_begin};
}
std::optional<Position> erase_last(Position p) const {
assert(valid(p));
if (p.row + 1 < blocks_[column_block_[p.column]].row_begin + height_[p.column]) return Position{p.row + 1, p.column};
int column = down_[p.column];
if (column == -1) return std::nullopt;
return Position{blocks_[column_block_[column]].row_begin, column};
}
std::uint64_t distinct_substrings() const { return distinct_; }
};
#endif
#line 1 "string/basic_substring_structure.hpp"
#line 1 "components/suffix_locus.hpp"
#line 1 "string/suffix_tree.hpp"
#line 1 "components/suffix_array.hpp"
#include <algorithm>
#include <array>
#include <bit>
#include <cstdint>
#include <cassert>
#include <limits>
#include <numeric>
#include <string_view>
#include <vector>
#include <type_traits>
#if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__))
#include <immintrin.h>
#endif
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC push_options
#pragma GCC optimize("O3")
#endif
namespace suffix_array_detail {
#if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__))
__attribute__((target("avx2"))) inline void byte_seeds(const unsigned char *s, int n, std::vector<int> &seeds, std::vector<unsigned char> &marked) {
seeds.push_back(n), marked[n] = 1;
bool right = true;
int end = n;
const auto sign = _mm256_set1_epi8(static_cast<char>(0x80));
while (end >= 33) {
int begin = end - 32;
auto a = _mm256_loadu_si256(reinterpret_cast<const __m256i *>(s + begin));
auto next = _mm256_loadu_si256(reinterpret_cast<const __m256i *>(s + begin + 1));
auto previous = _mm256_loadu_si256(reinterpret_cast<const __m256i *>(s + begin - 1));
auto biased = _mm256_xor_si256(a, sign);
std::uint32_t equal = _mm256_movemask_epi8(_mm256_cmpeq_epi8(a, next));
std::uint32_t small = _mm256_movemask_epi8(_mm256_cmpgt_epi8(_mm256_xor_si256(next, sign), biased));
if (right) small |= equal & 0x80000000U;
for (int shift : {1, 2, 4, 8, 16}) {
small |= equal & (small >> shift);
equal &= equal >> shift;
}
std::uint32_t selected = small & std::uint32_t(_mm256_movemask_epi8(_mm256_cmpgt_epi8(_mm256_xor_si256(previous, sign), biased)));
while (selected) {
int bit = 31 - std::countl_zero(selected), p = begin + bit;
seeds.push_back(p), marked[p] = 1;
selected ^= std::uint32_t(1) << bit;
}
right = small & 1;
end = begin;
}
for (int i = end - 1; i > 0; --i) {
bool current = s[i] < s[i + 1] || (s[i] == s[i + 1] && right);
if (current && s[i - 1] > s[i]) seeds.push_back(i), marked[i] = 1;
right = current;
}
}
#endif
template<class Sequence>
bool prefix_order(const Sequence &s, int n, int alphabet, const std::vector<int> &seeds, std::vector<int> &ordered) {
if (seeds.size() <= 64 || alphabet > 257) return false;
int bits = std::bit_width(unsigned(alphabet - 1)), position_bits = std::bit_width(unsigned(n));
int length = (64 - position_bits) / bits;
std::uint64_t position_mask = (std::uint64_t(1) << position_bits) - 1;
auto key_at = [&](int p) {
std::uint64_t key = 0;
for (int k = 0; k < length; ++k) key = (key << bits) | (k < n - p ? unsigned(s[p + k]) : 0);
return key;
};
std::array<std::uint64_t, 128> sample;
int sample_size = std::min(128, static_cast<int>(seeds.size()));
for (int i = 0; i < sample_size; ++i) sample[i] = key_at(seeds[i]);
std::sort(sample.begin(), sample.begin() + sample_size);
for (int i = 1; i < sample_size; ++i) if (sample[i] == sample[i - 1]) return false;
std::vector<std::uint64_t> entries(seeds.size()), scratch(seeds.size());
for (std::size_t i = 0; i < seeds.size(); ++i) entries[i] = (key_at(seeds[i]) << position_bits) | unsigned(n - seeds[i]);
int first_shift = std::max(position_bits, bits * length + position_bits - 33);
for (int shift = first_shift; shift < bits * length + position_bits; shift += 11) {
std::array<int, 2048> count{};
for (auto entry : entries) ++count[(entry >> shift) & 2047];
int total = 0;
for (int &value : count) { int next = total + value; value = total; total = next; }
for (auto entry : entries) scratch[count[(entry >> shift) & 2047]++] = entry;
entries.swap(scratch);
}
for (std::size_t first = 0; first < entries.size();) {
std::size_t last = first + 1;
while (last < entries.size() && (entries[last] >> first_shift) == (entries[first] >> first_shift)) ++last;
if (last - first > 64) return false;
std::sort(entries.begin() + first, entries.begin() + last);
for (std::size_t i = first + 1; i < last; ++i) if ((entries[i] >> position_bits) == (entries[i - 1] >> position_bits)) return false;
first = last;
}
ordered.resize(seeds.size());
for (std::size_t i = 0; i < entries.size(); ++i) ordered[i] = n - static_cast<int>(entries[i] & position_mask);
return true;
}
template<class Sequence>
std::vector<int> encoded_sort(const Sequence &s, int n, int alphabet) {
std::vector<int> sa(std::size_t(n) + 1);
if (n < 16) {
std::iota(sa.begin(), sa.end(), 0);
std::sort(sa.begin(), sa.end(), [&](int a, int b) {
while (a < n && b < n && s[a] == s[b]) ++a, ++b;
return a != n ? b != n && s[a] < s[b] : b != n;
});
return sa;
}
std::vector<unsigned char> is_seed(std::size_t(n) + 1);
std::vector<int> seeds;
seeds.reserve(n / 2 + 1);
bool gathered = false;
#if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__))
if constexpr (std::is_same_v<Sequence, std::vector<unsigned char>>) {
if (n >= 64 && __builtin_cpu_supports("avx2")) {
byte_seeds(s.data(), n, seeds, is_seed);
gathered = true;
}
}
#endif
if (!gathered) {
bool next = true;
for (int i = n - 1; i >= 0; --i) {
bool current = s[i] == s[i + 1] ? next : s[i] < s[i + 1];
if (!current && next) seeds.push_back(i + 1), is_seed[i + 1] = 1;
next = current;
}
}
std::reverse(seeds.begin(), seeds.end());
std::vector<std::size_t> ends(alphabet), work(alphabet);
for (std::size_t i = 0; i < sa.size(); ++i) ++ends[s[i]];
std::partial_sum(ends.begin(), ends.end(), ends.begin());
auto induce = [&](const std::vector<int> &ordered) {
std::fill(sa.begin(), sa.end(), 0);
work = ends;
for (auto i = ordered.rbegin(); i != ordered.rend(); ++i) if (*i != n) sa[--work[s[*i]]] = *i + 1;
work[0] = 0;
std::copy(ends.begin(), ends.end() - 1, work.begin() + 1);
int last = n;
if (s[n - 2] < s[n - 1]) last = -last;
sa[work[s[n - 1]]++] = last;
for (std::size_t i = 1; i < sa.size(); ++i) {
int p = sa[i];
if (p > 1) {
int j = p - 2, c = s[j], v = j + 1;
if (!j || s[j - 1] < c) v = -v;
sa[work[c]++] = v;
}
}
work = ends;
for (int i = n; i > 0; --i) {
int p = sa[i];
sa[i] = (p < 0 ? -p : p) - 1;
if (p < -1) {
int j = -p - 2, c = s[j], v = j + 1;
if (j && s[j - 1] <= c) v = -v;
sa[--work[c]] = v;
}
}
sa[0] = n;
};
std::vector<int> ordered;
int used = alphabet;
while (used > 1 && ends[used - 1] == ends[used - 2]) --used;
if (prefix_order(s, n, used, seeds, ordered)) { induce(ordered); return sa; }
induce(seeds);
int m = static_cast<int>(seeds.size());
std::vector<int> sorted;
sorted.reserve(m);
for (int p : sa) if (is_seed[p]) sorted.push_back(p);
std::vector<int> names(std::size_t(n) / 2 + 1);
for (int i = 0; i + 1 < m; ++i) names[seeds[i] >> 1] = seeds[i + 1] - seeds[i] + 1;
names[n >> 1] = 1;
int rank = -1, previous = -1, previous_length = 0;
for (int p : sorted) {
int length = names[p >> 1];
bool different = previous < 0 || length != previous_length;
if (!different) for (int k = 0; k < length; ++k) if (s[p + k] != s[previous + k]) { different = true; break; }
rank += different;
names[p >> 1] = rank;
previous = p, previous_length = length;
}
std::vector<int> reduced(m), order(m);
for (int i = 0; i < m; ++i) reduced[i] = names[seeds[i] >> 1];
if (rank + 1 == m) for (int i = 0; i < m; ++i) order[reduced[i]] = i;
else order = encoded_sort(reduced, m - 1, rank + 1);
for (int i = 0; i < m; ++i) sorted[i] = seeds[order[i]];
induce(sorted);
return sa;
}
template<class Sequence>
std::vector<int> induced_sort(const Sequence &s, int upper) {
struct Encoded {
const Sequence &value;
int n;
int operator[](int i) const { return i == n ? 0 : value[i] + 1; }
};
int n = static_cast<int>(s.size());
if (n < 2) return n ? std::vector<int>{0} : std::vector<int>{};
int common = 1;
while (common < n && s[common] == s[0]) ++common;
if (common == n) {
std::vector<int> sa(n);
std::iota(sa.rbegin(), sa.rend(), 0);
return sa;
}
auto sa = encoded_sort(Encoded{s, n}, n, upper + 2);
sa.erase(sa.begin());
return sa;
}
inline std::vector<int> bytes(std::string_view s) {
assert(s.size() <= std::size_t(std::numeric_limits<int>::max()));
if (s.empty()) return {};
std::array<int, 256> codes{};
for (unsigned char c : s) codes[c] = 1;
int alphabet = 0;
for (int &code : codes) if (code) code = ++alphabet;
int n = static_cast<int>(s.size());
if (alphabet == 1) {
std::vector<int> sa(n);
std::iota(sa.rbegin(), sa.rend(), 0);
return sa;
}
auto build = [&]<class Symbol>() {
std::vector<Symbol> text(s.size() + 1);
for (int i = 0; i < n; ++i) text[i] = static_cast<Symbol>(codes[static_cast<unsigned char>(s[i])]);
auto sa = encoded_sort(text, n, alphabet + 1);
sa.erase(sa.begin());
return sa;
};
return alphabet < 256 ? build.template operator()<unsigned char>() : build.template operator()<std::uint16_t>();
}
}
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC pop_options
#endif
#line 1 "components/sequence_compare.hpp"
#line 5 "components/sequence_compare.hpp"
#include <cstddef>
#line 7 "components/sequence_compare.hpp"
#include <cstring>
#line 9 "components/sequence_compare.hpp"
#if defined(__x86_64__)
#include <immintrin.h>
#endif
namespace sequence_compare_detail {
#if defined(__x86_64__)
__attribute__((target("avx2"))) inline std::size_t forward_avx2(const unsigned char *a, const unsigned char *b, std::size_t size) {
std::size_t i = 0;
for (; size - i >= 32; i += 32) {
auto x = _mm256_loadu_si256(reinterpret_cast<const __m256i *>(a + i));
auto y = _mm256_loadu_si256(reinterpret_cast<const __m256i *>(b + i));
auto difference = ~std::uint32_t(_mm256_movemask_epi8(_mm256_cmpeq_epi8(x, y)));
if (difference) return i + std::countr_zero(difference);
}
while (i < size && a[i] == b[i]) ++i;
return i;
}
__attribute__((target("avx2"))) inline std::size_t backward_avx2(const unsigned char *a, const unsigned char *b, std::size_t size) {
std::size_t i = 0;
for (; size - i >= 32; i += 32) {
auto x = _mm256_loadu_si256(reinterpret_cast<const __m256i *>(a - i - 32));
auto y = _mm256_loadu_si256(reinterpret_cast<const __m256i *>(b - i - 32));
auto difference = ~std::uint32_t(_mm256_movemask_epi8(_mm256_cmpeq_epi8(x, y)));
if (difference) return i + std::countl_zero(difference);
}
while (i < size && a[-std::ptrdiff_t(i) - 1] == b[-std::ptrdiff_t(i) - 1]) ++i;
return i;
}
#endif
template<bool Vectorize = true, class T>
inline int prefix(const T *first, const T *second, int limit) {
if (!limit) return 0;
int matched = 0;
if constexpr (std::is_integral_v<T> && std::has_unique_object_representations_v<T>) {
const auto *a = reinterpret_cast<const unsigned char *>(first);
const auto *b = reinterpret_cast<const unsigned char *>(second);
std::size_t offset = 0, size = std::size_t(limit) * sizeof(T);
while (size - offset >= 8) {
std::uint64_t x, y;
std::memcpy(&x, a + offset, 8);
std::memcpy(&y, b + offset, 8);
auto difference = x ^ y;
if (difference) {
int bits = std::endian::native == std::endian::little ? std::countr_zero(difference) : std::countl_zero(difference);
return static_cast<int>((offset + bits / 8) / sizeof(T));
}
offset += 8;
#if defined(__x86_64__)
if constexpr (Vectorize) if (size - offset >= 64 && __builtin_cpu_supports("avx2"))
return static_cast<int>((offset + forward_avx2(a + offset, b + offset, size - offset)) / sizeof(T));
#endif
}
matched = static_cast<int>(offset / sizeof(T));
}
while (matched < limit && first[matched] == second[matched]) ++matched;
return matched;
}
template<bool Vectorize = true, class T>
inline int suffix(const T *first_end, const T *second_end, int limit) {
if (!limit) return 0;
int matched = 0;
if constexpr (std::is_integral_v<T> && std::has_unique_object_representations_v<T>) {
const auto *a = reinterpret_cast<const unsigned char *>(first_end);
const auto *b = reinterpret_cast<const unsigned char *>(second_end);
std::size_t offset = 0, size = std::size_t(limit) * sizeof(T);
while (size - offset >= 8) {
std::uint64_t x, y;
std::memcpy(&x, a - offset - 8, 8);
std::memcpy(&y, b - offset - 8, 8);
auto difference = x ^ y;
if (difference) {
int bits = std::endian::native == std::endian::little ? std::countl_zero(difference) : std::countr_zero(difference);
return static_cast<int>((offset + bits / 8) / sizeof(T));
}
offset += 8;
#if defined(__x86_64__)
if constexpr (Vectorize) if (size - offset >= 64 && __builtin_cpu_supports("avx2"))
return static_cast<int>((offset + backward_avx2(a - offset, b - offset, size - offset)) / sizeof(T));
#endif
}
matched = static_cast<int>(offset / sizeof(T));
}
while (matched < limit && first_end[-matched - 1] == second_end[-matched - 1]) ++matched;
return matched;
}
}
#line 1 "components/static_range_min.hpp"
#line 1 "components/default_init_allocator.hpp"
#include <memory>
#include <new>
template <class T>
struct DefaultInitAllocator : std::allocator<T> {
template <class U>
struct rebind {
using other = DefaultInitAllocator<U>;
};
DefaultInitAllocator() = default;
template <class U>
DefaultInitAllocator(const DefaultInitAllocator<U> &) noexcept {}
template <class U>
void construct(U *p) { ::new (static_cast<void *>(p)) U; }
};
#line 1 "data_structure/sparse_table.hpp"
#line 12 "data_structure/sparse_table.hpp"
#include <utility>
#line 14 "data_structure/sparse_table.hpp"
template <class S, auto op, auto e = nullptr>
class SparseTable {
using Value = std::conditional_t<std::is_same_v<S, bool>, std::uint8_t, S>;
using Reference = std::conditional_t<std::is_same_v<S, bool>, bool, const S &>;
int n = 0;
std::vector<std::vector<Value, DefaultInitAllocator<Value>>> data;
static decltype(auto) combine(const Value &a, const Value &b) {
if constexpr (std::is_same_v<S, bool> || !std::is_invocable_v<decltype(op), const S &, const S &>) return S(op(S(a), S(b)));
else return op(a, b);
}
public:
SparseTable() = default;
SparseTable(const SparseTable &) = default;
SparseTable &operator=(const SparseTable &) = default;
SparseTable(SparseTable &&other) noexcept : n(std::exchange(other.n, 0)), data(std::move(other.data)) {}
SparseTable &operator=(SparseTable &&other) noexcept {
if (this != &other) {
n = std::exchange(other.n, 0);
data = std::move(other.data);
}
return *this;
}
explicit SparseTable(const std::vector<S> &a) { build(a); }
void clear() {
data.clear();
n = 0;
}
void build(const std::vector<S> &a) {
assert(a.size() <= std::size_t(std::numeric_limits<int>::max()));
build(static_cast<int>(a.size()), [&](int i) -> S { return a[i]; });
}
template <class F>
void build(int size, F value) {
assert(size >= 0);
n = size;
data.resize(std::bit_width(unsigned(n)));
if (!n) return;
if constexpr (std::is_default_constructible_v<Value>) {
data[0].resize(n);
for (int i = 0; i < n; ++i) data[0][i] = value(i);
} else {
data[0].clear();
data[0].reserve(n);
for (int i = 0; i < n; ++i) data[0].push_back(value(i));
}
for (int k = 1; k < std::bit_width(unsigned(n)); ++k) {
int length = n - (1U << k) + 1, half = 1U << (k - 1);
if constexpr (std::is_default_constructible_v<Value>) {
data[k].resize(length);
for (int i = 0; i < length; ++i) data[k][i] = combine(data[k - 1][i], data[k - 1][i + half]);
} else {
data[k].clear();
data[k].reserve(length);
for (int i = 0; i < length; ++i) data[k].push_back(combine(data[k - 1][i], data[k - 1][i + half]));
}
}
}
int size() const { return n; }
bool empty() const { return n == 0; }
Reference operator[](int i) const {
assert(0 <= i && i < n);
return data[0][i];
}
[[gnu::always_inline]] S prod(int l, int r) const {
assert(0 <= l && l <= r && r <= n);
if constexpr (std::is_same_v<decltype(e), std::nullptr_t>) assert(l < r);
else if (l == r) return e();
return prod_unchecked(l, r);
}
[[gnu::always_inline]] S prod_unchecked(int l, int r) const {
int k = std::bit_width(unsigned(r - l)) - 1;
return combine(data[k][l], data[k][r - (1U << k)]);
}
S all_prod() const { return prod(0, n); }
};
#line 12 "components/static_range_min.hpp"
#include <span>
#line 16 "components/static_range_min.hpp"
template <class T = int, bool FastBoundaries = false> class StaticRangeMin {
static T minimum(T a, T b) { return b < a ? b : a; }
std::vector<T> values_;
std::vector<std::uint32_t, DefaultInitAllocator<std::uint32_t>> masks_;
SparseTable<T, minimum> blocks_;
struct Boundaries { std::vector<T> prefix, suffix; };
struct Empty {};
[[no_unique_address]] std::conditional_t<FastBoundaries, Boundaries, Empty> boundaries_;
T within(int l, int r) const {
auto mask = masks_[r - 1] & (~std::uint32_t(0) << (l & 31));
return values_[(l & ~31) + std::countr_zero(mask)];
}
public:
StaticRangeMin() = default;
explicit StaticRangeMin(std::vector<T> values) { build(std::move(values)); }
void build(std::vector<T> values) {
assert(values.size() <= std::size_t(std::numeric_limits<int>::max()));
values_ = std::move(values);
int n = size();
masks_.resize(n);
for (int first = 0; first < n;) {
int last = first + std::min(32, n - first);
std::uint32_t mask = 0;
for (int i = first; i < last; ++i) {
while (mask && !(values_[first + std::bit_width(mask) - 1] < values_[i]))
mask &= ~(std::uint32_t(1) << (std::bit_width(mask) - 1));
mask |= std::uint32_t(1) << (i - first);
masks_[i] = mask;
}
first = last;
}
if constexpr (FastBoundaries) {
boundaries_.prefix = values_, boundaries_.suffix = values_;
for (int first = 0; first < n;) {
int last = first + std::min(32, n - first);
for (int i = first + 1; i < last; ++i) boundaries_.prefix[i] = minimum(boundaries_.prefix[i - 1], values_[i]);
for (int i = last - 1; i > first; --i) boundaries_.suffix[i - 1] = minimum(values_[i - 1], boundaries_.suffix[i]);
first = last;
}
}
blocks_.build(n ? (n - 1) / 32 + 1 : 0, [&](int i) {
int last = i * 32 + std::min(32, n - i * 32);
if constexpr (FastBoundaries) return T(boundaries_.prefix[last - 1]);
else return within(i * 32, last);
});
}
int size() const { return static_cast<int>(values_.size()); }
bool empty() const { return values_.empty(); }
std::conditional_t<std::is_same_v<T, bool>, bool, const T &> operator[](int i) const {
assert(0 <= i && i < size());
return values_[i];
}
T prod(int l, int r) const {
assert(0 <= l && l < r && r <= size());
int a = l / 32, b = (r - 1) / 32;
if (a == b) return within(l, r);
T value = [&] {
if constexpr (FastBoundaries) return minimum(boundaries_.suffix[l], boundaries_.prefix[r - 1]);
else return minimum(within(l, (a + 1) * 32), within(b * 32, r));
}();
if (a + 1 < b) value = minimum(value, blocks_.prod_unchecked(a + 1, b));
return value;
}
std::vector<T> prod_many(std::span<const std::pair<int, int>> ranges) const {
std::vector<T> result;
result.reserve(ranges.size());
if constexpr (FastBoundaries) {
for (auto [l, r] : ranges) result.emplace_back(prod(l, r));
} else {
std::array<int, 32> left, right;
for (std::size_t first = 0; first < ranges.size(); first += 32) {
int count = int(std::min<std::size_t>(32, ranges.size() - first));
for (int i = 0; i < count; ++i) {
auto [l, r] = ranges[first + i];
assert(0 <= l && l < r && r <= size());
int a = l & ~31, b = (r - 1) & ~31;
auto mask = masks_[a == b ? r - 1 : a + 31] & (~std::uint32_t(0) << (l & 31));
left[i] = a + std::countr_zero(mask);
right[i] = a == b ? left[i] : b + std::countr_zero(masks_[r - 1]);
#if defined(__GNUC__) || defined(__clang__)
if constexpr (!std::is_same_v<T, bool>) {
__builtin_prefetch(values_.data() + left[i]);
__builtin_prefetch(values_.data() + right[i]);
}
#endif
}
for (int i = 0; i < count; ++i) {
auto [l, r] = ranges[first + i];
T value = minimum(values_[left[i]], values_[right[i]]);
int a = l / 32 + 1, b = (r - 1) / 32;
if (a < b) value = minimum(value, blocks_.prod_unchecked(a, b));
result.emplace_back(std::move(value));
}
}
}
return result;
}
T all_prod() const { return prod(0, size()); }
};
#line 9 "string/suffix_tree.hpp"
#include <optional>
#line 11 "string/suffix_tree.hpp"
#include <string>
#line 13 "string/suffix_tree.hpp"
class SuffixTree {
struct Node { int parent, depth, left, right; };
std::string text_;
mutable std::vector<Node> nodes_;
std::vector<int> sa_;
mutable std::vector<int> lcp_, rank_;
mutable std::vector<int> terminals_, branches_, offset_, children_;
mutable std::vector<unsigned char> symbols_;
mutable std::array<int, 256> root_children_{};
mutable bool lcp_ready_ = false, tree_ready_ = false;
mutable std::uint64_t distinct_ = 0;
mutable std::optional<StaticRangeMin<std::pair<int, int>>> index_;
void check([[maybe_unused]] int v) const { prepare_tree(); assert(0 <= v && v < static_cast<int>(nodes_.size())); }
int range_end(int v) const { int r = nodes_[v].right; return r < 0 ? nodes_[-r - 1].right : r; }
void prepare_lcp() const {
if (lcp_ready_) return;
int n = size();
rank_.resize(n + 1);
for (int i = 0; i <= n; ++i) rank_[sa_[i]] = i;
lcp_.assign(n, 0);
for (int p = 0, h = 0; p < n; ++p) {
if (h) --h;
int i = rank_[p], q = sa_[i - 1];
h += sequence_compare_detail::prefix(text_.data() + p + h, text_.data() + q + h, std::min(n - p, n - q) - h);
lcp_[i - 1] = h;
}
distinct_ = std::uint64_t(n) * (n + 1) / 2;
for (int value : lcp_) distinct_ -= value;
lcp_ready_ = true;
}
void prepare_tree() const {
if (tree_ready_) return;
prepare_lcp();
int n = size();
nodes_.clear();
nodes_.reserve(std::size_t(n) + 1);
nodes_.push_back({-1, 0, 0, n + 1});
terminals_.assign(n + 1, 0);
branches_.resize(n);
std::vector<int> stack{0};
for (int i = 1; i <= n; ++i) {
int common = lcp_[i - 1], last = -1;
while (nodes_[stack.back()].depth > common) { last = stack.back(); nodes_[last].right = i; stack.pop_back(); }
if (nodes_[stack.back()].depth < common) {
int v = static_cast<int>(nodes_.size());
nodes_.push_back({stack.back(), common, nodes_[last].left, n + 1});
nodes_[last].parent = v;
stack.push_back(v);
}
branches_[i - 1] = stack.back();
int v = static_cast<int>(nodes_.size()), p = sa_[i];
nodes_.push_back({stack.back(), n - p, i, n + 1});
terminals_[p] = v;
stack.push_back(v);
}
int count = static_cast<int>(nodes_.size());
offset_.assign(nodes_.size() + 1, 0);
for (int v = 1; v < count; ++v) ++offset_[nodes_[v].parent + 1];
for (int v = 0; v < count; ++v) offset_[v + 1] += offset_[v];
children_.resize(nodes_.size() - 1);
symbols_.resize(children_.size());
auto cursor = offset_;
for (int v = 1; v < count; ++v) {
int p = nodes_[v].parent, i = cursor[p]++;
children_[i] = v;
symbols_[i] = static_cast<unsigned char>(text_[sa_[nodes_[v].left] + nodes_[p].depth]);
}
root_children_.fill(-1);
for (int i = offset_[0]; i < offset_[1]; ++i) {
int v = children_[i];
root_children_[static_cast<unsigned char>(text_[sa_[nodes_[v].left]])] = v;
}
for (int i = n; i >= 0; --i) {
int v = terminals_[sa_[i]];
if (offset_[v + 1] - offset_[v] == 1) {
int u = children_[offset_[v]];
nodes_[v].right = nodes_[u].right < 0 ? nodes_[u].right : -u - 1;
}
}
tree_ready_ = true;
}
void prepare_index() const {
prepare_tree();
if (index_) return;
std::vector<std::pair<int, int>> values(size());
for (int i = 0; i < size(); ++i) values[i] = {lcp_[i], branches_[i]};
index_.emplace(std::move(values));
}
int child(int v, unsigned char c) const {
if (!v) return root_children_[c];
int l = offset_[v], r = offset_[v + 1];
if (r - l <= 4) {
for (int i = l; i < r; ++i) if (symbols_[i] == c) return children_[i];
return -1;
}
int end = r;
while (l < r) { int m = l + (r - l) / 2; if (symbols_[m] < c) l = m + 1; else r = m; }
return l < end && symbols_[l] == c ? children_[l] : -1;
}
public:
struct Locus { int node, length; };
static constexpr int root = 0;
SuffixTree() { build({}); }
explicit SuffixTree(std::string_view s) { build(s); }
SuffixTree(const SuffixTree &) = default;
SuffixTree &operator=(const SuffixTree &) = default;
SuffixTree(SuffixTree &&other) : SuffixTree() { swap(other); }
SuffixTree &operator=(SuffixTree &&other) {
if (this != &other) { SuffixTree moved(std::move(other)); swap(moved); }
return *this;
}
void swap(SuffixTree &other) noexcept {
text_.swap(other.text_);
nodes_.swap(other.nodes_);
sa_.swap(other.sa_); lcp_.swap(other.lcp_); rank_.swap(other.rank_);
terminals_.swap(other.terminals_); branches_.swap(other.branches_);
offset_.swap(other.offset_); children_.swap(other.children_);
symbols_.swap(other.symbols_);
root_children_.swap(other.root_children_);
std::swap(distinct_, other.distinct_);
std::swap(lcp_ready_, other.lcp_ready_);
std::swap(tree_ready_, other.tree_ready_);
index_.swap(other.index_);
}
void build(std::string_view s) {
assert(s.size() < std::size_t(std::numeric_limits<int>::max() / 2));
std::string copy(s);
text_.swap(copy);
int n = size();
sa_ = suffix_array_detail::bytes(text_);
sa_.insert(sa_.begin(), n);
rank_.clear(); lcp_.clear();
lcp_ready_ = false;
nodes_.clear(); terminals_.clear(); branches_.clear(); offset_.clear(); children_.clear(); symbols_.clear();
tree_ready_ = false;
index_.reset();
}
void clear() { build({}); }
int size() const { return static_cast<int>(text_.size()); }
bool empty() const { return text_.empty(); }
int node_count() const { prepare_tree(); return static_cast<int>(nodes_.size()); }
std::string_view text() const { return text_; }
int parent(int v) const { check(v); return nodes_[v].parent; }
int depth(int v) const { check(v); return nodes_[v].depth; }
std::pair<int, int> edge_range(int v) const {
check(v);
if (!v) return {0, 0};
int p = sa_[nodes_[v].left];
return {p + nodes_[nodes_[v].parent].depth, p + nodes_[v].depth};
}
std::pair<int, int> label_range(int v) const { check(v); int p = sa_[nodes_[v].left]; return {p, p + nodes_[v].depth}; }
std::span<const int> children(int v) const {
check(v);
int count = offset_[v + 1] - offset_[v];
return count ? std::span<const int>(children_.data() + offset_[v], count) : std::span<const int>{};
}
int transition(int v, char c) const { check(v); return child(v, static_cast<unsigned char>(c)); }
int suffix_start(int v) const { check(v); int p = sa_[nodes_[v].left]; return size() - p == nodes_[v].depth ? p : -1; }
int suffix_node(int p) const { prepare_tree(); assert(0 <= p && p <= size()); return terminals_[p]; }
std::pair<int, int> occurrence_range(int v) const { check(v); return {nodes_[v].left, range_end(v)}; }
int occurrence_count(int v) const { check(v); return range_end(v) - nodes_[v].left; }
const std::vector<int> &suffix_array() const { return sa_; }
const std::vector<int> &lcp_array() const { prepare_lcp(); return lcp_; }
std::uint64_t distinct_substrings() const { prepare_lcp(); return distinct_; }
std::optional<Locus> find(std::string_view pattern) const {
prepare_tree();
if (pattern.size() > text_.size()) return std::nullopt;
int v = 0, matched = 0, length = static_cast<int>(pattern.size());
while (matched < length) {
int u = child(v, static_cast<unsigned char>(pattern[matched]));
if (u == -1) return std::nullopt;
if (nodes_[u].right >= 0) {
int start = sa_[nodes_[u].left] + matched;
int take = std::min(length - matched, nodes_[u].depth - matched);
if (sequence_compare_detail::prefix(text_.data() + start, pattern.data() + matched, take) != take) return std::nullopt;
matched += take;
v = u;
continue;
}
int end = -nodes_[u].right - 1;
int start = sa_[nodes_[end].left] + nodes_[v].depth;
int take = std::min(length - matched, nodes_[end].depth - nodes_[v].depth);
if (sequence_compare_detail::prefix(text_.data() + start, pattern.data() + matched, take) != take) return std::nullopt;
matched += take;
if (matched < nodes_[end].depth && nodes_[u].depth < matched) {
int l = nodes_[u].left + 1, r = std::min(nodes_[end].left, nodes_[u].left + matched - nodes_[u].depth);
while (l < r) { int m = l + (r - l) / 2; if (size() - sa_[m] < matched) l = m + 1; else r = m; }
v = l == nodes_[end].left ? end : terminals_[sa_[l]];
} else v = matched <= nodes_[u].depth ? u : end;
}
return Locus{v, length};
}
std::optional<Locus> advance(Locus locus, char c) const {
check(locus.node);
int v = locus.node;
assert((!v && locus.length == 0) || (v && nodes_[nodes_[v].parent].depth < locus.length && locus.length <= nodes_[v].depth));
if (locus.length < nodes_[v].depth) {
if (text_[sa_[nodes_[v].left] + locus.length] != c) return std::nullopt;
return Locus{v, locus.length + 1};
}
int u = child(v, static_cast<unsigned char>(c));
return u == -1 ? std::nullopt : std::optional<Locus>(Locus{u, locus.length + 1});
}
bool contains(std::string_view pattern) const { return find(pattern).has_value(); }
int count(std::string_view pattern) const { auto locus = find(pattern); return locus ? occurrence_count(locus->node) : 0; }
std::vector<int> occurrences(std::string_view pattern) const {
auto locus = find(pattern);
if (!locus) return {};
auto [l, r] = occurrence_range(locus->node);
return {sa_.begin() + l, sa_.begin() + r};
}
int lcp(int p, int q) const {
assert(0 <= p && p <= size() && 0 <= q && q <= size());
if (p == q) return size() - p;
prepare_lcp();
int a = rank_[p], b = rank_[q];
if (a > b) std::swap(a, b);
prepare_index();
return index_->prod(a, b).first;
}
int lca(int u, int v) const {
check(u); check(v);
int end_u = range_end(u), end_v = range_end(v);
if (nodes_[u].left <= nodes_[v].left && end_v <= end_u) return u;
if (nodes_[v].left <= nodes_[u].left && end_u <= end_v) return v;
int a = nodes_[u].left, b = nodes_[v].left;
if (a > b) std::swap(a, b);
prepare_index();
return index_->prod(a, b).second;
}
int suffix_link(int v) const {
check(v);
if (!v) return 0;
int p = suffix_start(v);
if (p >= 0) return terminals_[p + 1];
return lca(terminals_[sa_[nodes_[v].left] + 1], terminals_[sa_[range_end(v) - 1] + 1]);
}
};
#line 5 "components/suffix_locus.hpp"
class SuffixLocusIndex {
struct Node { int parent, depth, head, vertex; };
std::vector<Node> nodes_;
std::vector<int> terminal_;
public:
SuffixLocusIndex() = default;
explicit SuffixLocusIndex(const SuffixTree &tree) { build(tree); }
SuffixLocusIndex(const SuffixLocusIndex &) = default;
SuffixLocusIndex &operator=(const SuffixLocusIndex &) = default;
SuffixLocusIndex(SuffixLocusIndex &&other) noexcept { swap(other); }
SuffixLocusIndex &operator=(SuffixLocusIndex &&other) noexcept {
if (this != &other) { SuffixLocusIndex moved(std::move(other)); swap(moved); }
return *this;
}
void swap(SuffixLocusIndex &other) noexcept {
nodes_.swap(other.nodes_);
terminal_.swap(other.terminal_);
}
void build(const SuffixTree &tree) {
SuffixLocusIndex result;
int count = tree.node_count();
std::vector<int> heavy(count, -1), position(count);
for (int v = 0; v < count; ++v)
for (int u : tree.children(v))
if (heavy[v] == -1 || tree.occurrence_count(u) > tree.occurrence_count(heavy[v])) heavy[v] = u;
result.nodes_.clear();
result.nodes_.reserve(count);
std::vector<std::pair<int, int>> stack{{0, -1}};
while (!stack.empty()) {
auto [v, parent] = stack.back();
stack.pop_back();
int head = int(result.nodes_.size());
while (v != -1) {
int i = int(result.nodes_.size());
result.nodes_.push_back({parent, tree.depth(v), head, v});
position[v] = i;
for (int u : tree.children(v)) if (u != heavy[v]) stack.emplace_back(u, i);
parent = i;
v = heavy[v];
}
}
result.terminal_.resize(std::size_t(tree.size()) + 1);
for (int p = 0; p <= tree.size(); ++p) result.terminal_[p] = position[tree.suffix_node(p)];
swap(result);
}
int size() const { return terminal_.empty() ? 0 : int(terminal_.size()) - 1; }
SuffixTree::Locus locate(int l, int r) const {
assert(0 <= l && l <= r && r <= size());
int length = r - l;
if (!length) return {0, 0};
int v = terminal_[l];
for (;;) {
int p = nodes_[nodes_[v].head].parent;
if (p == -1 || nodes_[p].depth < length) break;
v = p;
}
int a = nodes_[v].head, b = v;
while (a < b) {
int m = a + (b - a) / 2;
if (nodes_[m].depth < length) a = m + 1;
else b = m;
}
return {nodes_[a].vertex, length};
}
};
#line 11 "string/basic_substring_structure.hpp"
class BasicSubstringStructure {
public:
struct Position {
int row, column;
friend bool operator==(const Position &, const Position &) = default;
};
struct Block {
int left, right, row_begin, row_end, column_begin, column_end, occurrences;
};
private:
std::string text_;
SuffixLocusIndex index_;
std::vector<Block> blocks_;
std::vector<int> row_block_, column_block_, width_, height_, right_, down_, node_column_;
std::uint64_t distinct_ = 0;
void construct(std::string_view text) {
assert(text.size() < std::size_t(std::numeric_limits<int>::max() / 2));
text_ = text;
if (text_.empty()) return;
int n = size();
SuffixTree forward(text_), backward(std::string(text_.rbegin(), text_.rend()));
index_.build(forward);
SuffixLocusIndex reverse_index(backward);
int columns = forward.node_count() - 1, rows = backward.node_count() - 1;
std::vector<int> order{0}, low(columns + 1, 257), high(columns + 1, -1);
order.reserve(columns + 1);
for (std::size_t i = 0; i < order.size(); ++i) {
int v = order[i], p = forward.suffix_start(v);
if (p >= 0) low[v] = high[v] = p ? static_cast<unsigned char>(text_[p - 1]) + 1 : 0;
for (int u : forward.children(v)) order.push_back(u);
}
for (std::size_t i = order.size(); i-- > 1;) {
int v = order[i], p = forward.parent(v);
low[p] = std::min(low[p], low[v]);
high[p] = std::max(high[p], high[v]);
}
std::vector<int> heads, counts(n + 1);
for (int v = 1; v <= columns; ++v) {
if (low[v] != high[v] || low[v] == 0) {
heads.push_back(v);
++counts[forward.occurrence_count(v)];
}
}
int total = 0;
for (int count = n; count > 0; --count) {
int next = total + counts[count];
counts[count] = total;
total = next;
}
order.resize(heads.size());
for (int v : heads) order[counts[forward.occurrence_count(v)]++] = v;
blocks_.reserve(heads.size());
row_block_.reserve(rows); width_.reserve(rows); right_.reserve(rows);
column_block_.reserve(columns); height_.reserve(columns); down_.reserve(columns);
node_column_.assign(columns + 1, -1);
std::vector<int> node_row(rows + 1, -1);
auto suffix_link = [](const SuffixTree &tree, const SuffixLocusIndex &index, int v) {
int p = tree.suffix_start(v);
if (p >= 0) return tree.suffix_node(p + 1);
auto [l, r] = tree.label_range(v);
return index.locate(l + 1, r).node;
};
for (int v : order) {
auto [l, r] = forward.label_range(v);
int u = reverse_index.locate(n - r, n - l).node;
assert(backward.depth(u) == r - l);
int h = forward.depth(v) - forward.depth(forward.parent(v));
int w = backward.depth(u) - backward.depth(backward.parent(u));
int row = static_cast<int>(row_block_.size()), column = static_cast<int>(column_block_.size());
int id = block_count(), count = forward.occurrence_count(v);
blocks_.push_back({l, r, row, row + h, column, column + w, count});
for (int i = 0, a = u; i < h; ++i) {
assert(a > 0 && node_row[a] == -1 && backward.occurrence_count(a) == count);
node_row[a] = row + i;
row_block_.push_back(id);
width_.push_back(backward.depth(a) - backward.depth(backward.parent(a)));
right_.push_back(backward.parent(a));
distinct_ += width_.back();
if (i + 1 < h) a = suffix_link(backward, reverse_index, a);
}
for (int i = 0, a = v; i < w; ++i) {
assert(a > 0 && node_column_[a] == -1 && forward.occurrence_count(a) == count);
node_column_[a] = column + i;
column_block_.push_back(id);
height_.push_back(forward.depth(a) - forward.depth(forward.parent(a)));
down_.push_back(forward.parent(a));
if (i + 1 < w) a = suffix_link(forward, index_, a);
}
}
assert(row_block_.size() == std::size_t(rows) && column_block_.size() == std::size_t(columns));
for (int &v : right_) v = node_row[v];
for (int &v : down_) v = node_column_[v];
}
public:
BasicSubstringStructure() = default;
explicit BasicSubstringStructure(std::string_view text) { construct(text); }
BasicSubstringStructure(const BasicSubstringStructure &) = default;
BasicSubstringStructure &operator=(const BasicSubstringStructure &) = default;
BasicSubstringStructure(BasicSubstringStructure &&other) noexcept { swap(other); }
BasicSubstringStructure &operator=(BasicSubstringStructure &&other) noexcept {
if (this != &other) { BasicSubstringStructure moved(std::move(other)); swap(moved); }
return *this;
}
void swap(BasicSubstringStructure &other) noexcept {
text_.swap(other.text_);
index_.swap(other.index_);
blocks_.swap(other.blocks_);
row_block_.swap(other.row_block_); column_block_.swap(other.column_block_);
width_.swap(other.width_); height_.swap(other.height_);
right_.swap(other.right_); down_.swap(other.down_); node_column_.swap(other.node_column_);
std::swap(distinct_, other.distinct_);
}
void build(std::string_view text) { BasicSubstringStructure next(text); swap(next); }
void clear() { BasicSubstringStructure next; swap(next); }
int size() const { return static_cast<int>(text_.size()); }
bool empty() const { return text_.empty(); }
std::string_view text() const { return text_; }
int block_count() const { return static_cast<int>(blocks_.size()); }
std::span<const Block> blocks() const { return blocks_; }
const Block &block(int id) const { assert(0 <= id && id < block_count()); return blocks_[id]; }
std::pair<int, int> shape() const { return {static_cast<int>(row_block_.size()), static_cast<int>(column_block_.size())}; }
int row_block(int row) const { assert(0 <= row && row < shape().first); return row_block_[row]; }
int column_block(int column) const { assert(0 <= column && column < shape().second); return column_block_[column]; }
int width(int row) const { assert(0 <= row && row < shape().first); return width_[row]; }
int height(int column) const { assert(0 <= column && column < shape().second); return height_[column]; }
int right(int row) const { assert(0 <= row && row < shape().first); return right_[row]; }
int down(int column) const { assert(0 <= column && column < shape().second); return down_[column]; }
bool valid(Position p) const {
if (p.row < 0 || p.row >= shape().first || p.column < 0 || p.column >= shape().second) return false;
int id = row_block_[p.row];
return column_block_[p.column] == id && p.column - blocks_[id].column_begin < width_[p.row];
}
std::optional<Position> locate(int l, int r) const {
assert(0 <= l && l <= r && r <= size());
if (l == r) return std::nullopt;
int column = node_column_[index_.locate(l, r).node];
const Block &b = blocks_[column_block_[column]];
int row = b.row_begin + ((b.right - b.left) - (r - l) - (column - b.column_begin));
return Position{row, column};
}
std::pair<int, int> substring_range(Position p) const {
assert(valid(p));
const Block &b = blocks_[row_block_[p.row]];
return {b.left + (p.column - b.column_begin), b.right - (p.row - b.row_begin)};
}
int occurrence_count(Position p) const { assert(valid(p)); return blocks_[row_block_[p.row]].occurrences; }
std::optional<Position> erase_first(Position p) const {
assert(valid(p));
if (p.column + 1 < blocks_[row_block_[p.row]].column_begin + width_[p.row]) return Position{p.row, p.column + 1};
int row = right_[p.row];
if (row == -1) return std::nullopt;
return Position{row, blocks_[row_block_[row]].column_begin};
}
std::optional<Position> erase_last(Position p) const {
assert(valid(p));
if (p.row + 1 < blocks_[column_block_[p.column]].row_begin + height_[p.column]) return Position{p.row + 1, p.column};
int column = down_[p.column];
if (column == -1) return std::nullopt;
return Position{blocks_[column_block_[column]].row_begin, column};
}
std::uint64_t distinct_substrings() const { return distinct_; }
};