m1une's library

This documentation is automatically generated by online-judge-tools/verification-helper

View on GitHub

:heavy_check_mark: Longest Common Extension
(string/longest_common_extension.hpp)

Overview

LongestCommonExtension is a static structure for longest-common-extension queries on one sequence. It returns the longest common prefix length of suffixes starting at two positions, which is useful for substring comparison, runs, repetitions, and suffix-array based string problems.

The implementation builds a suffix array, the adjacent LCP array, and a sparse table over the LCP array. Queries are deterministic and do not use hashing.

Sequence must provide size(), operator[], begin(), and end(). Elements must be sortable for construction. The comparison helpers also require element <.

Methods

Let N = size().

Method Description Complexity
LongestCommonExtension() Creates an empty structure. O(1)
LongestCommonExtension(const Sequence& sequence) Copies sequence and builds the structure. O(N \log N)
LongestCommonExtension(Sequence&& sequence) Moves sequence and builds the structure. O(N \log N)
int size() const Returns N. O(1)
bool empty() const Returns whether N == 0. O(1)
const Sequence& sequence() const Returns the stored sequence. O(1)
const vector<int>& suffix_array() const Returns suffix starts in lexicographic order. O(1)
const vector<int>& rank() const Returns inverse suffix-array ranks. O(1)
const vector<int>& lcp_array() const Returns adjacent suffix LCP lengths. O(1)
int longest_common_extension(int i, int j) const Returns LCP(sequence[i..], sequence[j..]). O(1)
int longest_common_extension(int i, int j, int limit) const Returns min(longest_common_extension(i, j), limit). O(1)
int lcp(int i, int j) const Alias of longest_common_extension(i, j). O(1)
int operator()(int i, int j) const Alias of longest_common_extension(i, j). O(1)
int compare_suffix(int i, int j) const Lexicographically compares suffixes. O(1)
int compare(int l1, int r1, int l2, int r2) const Lexicographically compares two half-open substrings. O(1)

longest_common_extension(i, i) returns N - i. Positions i and j may be N, representing the empty suffix. Other indices must be in [0, N].

compare_suffix and compare return -1, 0, or 1.

The memory usage is O(N \log N).

Example

#include "string/longest_common_extension.hpp"

#include <iostream>
#include <string>

int main() {
    std::string text = "banana";
    m1une::string::LongestCommonExtension<> extension(text);

    std::cout << extension.longest_common_extension(1, 3) << "\n"; // 3
    std::cout << extension.compare(1, 4, 3, 6) << "\n";            // 0
}

Depends on

Required by

Verified with

Code

#ifndef M1UNE_STRING_LONGEST_COMMON_EXTENSION_HPP
#define M1UNE_STRING_LONGEST_COMMON_EXTENSION_HPP 1

#include <algorithm>
#include <cassert>
#include <string>
#include <utility>
#include <vector>

#include "suffix_array.hpp"

namespace m1une {
namespace string {

template <class Sequence = std::string>
struct LongestCommonExtension {
   private:
    Sequence _sequence;
    std::vector<int> _suffix_array;
    std::vector<int> _rank;
    std::vector<int> _lcp;
    std::vector<int> _log;
    std::vector<std::vector<int>> _table;

    int range_min(int left, int right) const {
        assert(0 <= left && left < right && right <= int(_lcp.size()));
        int k = _log[right - left];
        return std::min(_table[k][left], _table[k][right - (1 << k)]);
    }

    void build() {
        int n = int(_sequence.size());
        _suffix_array = m1une::string::suffix_array(_sequence);
        _rank.assign(n, 0);
        for (int i = 0; i < n; i++) {
            _rank[_suffix_array[i]] = i;
        }

        _lcp = m1une::string::lcp_array(_sequence, _suffix_array);
        int m = int(_lcp.size());
        _log.assign(m + 1, 0);
        for (int i = 2; i <= m; i++) {
            _log[i] = _log[i >> 1] + 1;
        }

        _table.clear();
        if (m == 0) return;
        _table.assign(_log[m] + 1, std::vector<int>());
        _table[0] = _lcp;
        for (int k = 1; k < int(_table.size()); k++) {
            int width = 1 << k;
            int half = width >> 1;
            _table[k].resize(m - width + 1);
            for (int i = 0; i + width <= m; i++) {
                _table[k][i] = std::min(_table[k - 1][i], _table[k - 1][i + half]);
            }
        }
    }

   public:
    LongestCommonExtension() = default;

    explicit LongestCommonExtension(const Sequence& sequence) : _sequence(sequence) {
        build();
    }

    explicit LongestCommonExtension(Sequence&& sequence) : _sequence(std::move(sequence)) {
        build();
    }

    int size() const {
        return int(_sequence.size());
    }

    bool empty() const {
        return _sequence.empty();
    }

    const Sequence& sequence() const {
        return _sequence;
    }

    const std::vector<int>& suffix_array() const {
        return _suffix_array;
    }

    const std::vector<int>& rank() const {
        return _rank;
    }

    const std::vector<int>& lcp_array() const {
        return _lcp;
    }

    int longest_common_extension(int i, int j) const {
        int n = size();
        assert(0 <= i && i <= n);
        assert(0 <= j && j <= n);
        if (i == j) return n - i;
        if (i == n || j == n) return 0;

        int left = _rank[i];
        int right = _rank[j];
        if (left > right) std::swap(left, right);
        return range_min(left, right);
    }

    int longest_common_extension(int i, int j, int limit) const {
        assert(0 <= limit);
        return std::min(longest_common_extension(i, j), limit);
    }

    int lcp(int i, int j) const {
        return longest_common_extension(i, j);
    }

    int operator()(int i, int j) const {
        return longest_common_extension(i, j);
    }

    int compare_suffix(int i, int j) const {
        int n = size();
        assert(0 <= i && i <= n);
        assert(0 <= j && j <= n);
        if (i == j) return 0;
        int common = longest_common_extension(i, j);
        if (i + common == n && j + common == n) return 0;
        if (i + common == n) return -1;
        if (j + common == n) return 1;
        return _sequence[i + common] < _sequence[j + common] ? -1 : 1;
    }

    int compare(int l1, int r1, int l2, int r2) const {
        int n = size();
        assert(0 <= l1 && l1 <= r1 && r1 <= n);
        assert(0 <= l2 && l2 <= r2 && r2 <= n);
        int len1 = r1 - l1;
        int len2 = r2 - l2;
        int common = longest_common_extension(l1, l2, std::min(len1, len2));
        if (common == len1 && common == len2) return 0;
        if (common == len1) return -1;
        if (common == len2) return 1;
        return _sequence[l1 + common] < _sequence[l2 + common] ? -1 : 1;
    }
};

}  // namespace string
}  // namespace m1une

#endif  // M1UNE_STRING_LONGEST_COMMON_EXTENSION_HPP
#line 1 "string/longest_common_extension.hpp"



#include <algorithm>
#include <cassert>
#include <string>
#include <utility>
#include <vector>

#line 1 "string/suffix_array.hpp"



#line 6 "string/suffix_array.hpp"
#include <numeric>
#line 8 "string/suffix_array.hpp"
#include <type_traits>
#line 10 "string/suffix_array.hpp"

namespace m1une {
namespace string {
namespace detail {

template <class Sequence>
std::vector<int> suffix_array_impl(const Sequence& sequence) {
    int n = int(sequence.size());
    if (n == 0) return {};

    using Value = std::remove_cv_t<std::remove_reference_t<decltype(sequence[0])>>;
    std::vector<Value> sorted(sequence.begin(), sequence.end());
    std::sort(sorted.begin(), sorted.end());
    sorted.erase(std::unique(sorted.begin(), sorted.end()), sorted.end());

    int length = n + 1;
    std::vector<int> order(length);
    std::vector<int> rank(length);
    std::vector<int> key(length);
    key[n] = 0;
    for (int i = 0; i < n; i++) {
        key[i] = int(std::lower_bound(sorted.begin(), sorted.end(), sequence[i]) - sorted.begin()) + 1;
    }

    int alphabet = int(sorted.size()) + 1;
    std::vector<int> count(std::max(length, alphabet), 0);
    for (int value : key) count[value]++;
    for (int i = 1; i < alphabet; i++) count[i] += count[i - 1];
    for (int i = length - 1; i >= 0; i--) order[--count[key[i]]] = i;

    int classes = 1;
    rank[order[0]] = 0;
    for (int i = 1; i < length; i++) {
        if (key[order[i - 1]] != key[order[i]]) classes++;
        rank[order[i]] = classes - 1;
    }

    std::vector<int> shifted(length);
    std::vector<int> next_rank(length);
    for (long long half = 1; half < length; half <<= 1) {
        for (int i = 0; i < length; i++) {
            long long position = order[i] - half;
            if (position < 0) position += length;
            shifted[i] = int(position);
        }

        count.assign(classes, 0);
        for (int position : shifted) count[rank[position]]++;
        for (int i = 1; i < classes; i++) count[i] += count[i - 1];
        for (int i = length - 1; i >= 0; i--) {
            int position = shifted[i];
            order[--count[rank[position]]] = position;
        }

        int next_classes = 1;
        next_rank[order[0]] = 0;
        for (int i = 1; i < length; i++) {
            int current = order[i];
            int previous = order[i - 1];
            int current_second = int((current + half) % length);
            int previous_second = int((previous + half) % length);
            if (
                rank[current] != rank[previous] ||
                rank[current_second] != rank[previous_second]
            ) {
                next_classes++;
            }
            next_rank[current] = next_classes - 1;
        }
        rank.swap(next_rank);
        classes = next_classes;
        if (classes == length) break;
    }

    std::vector<int> suffixes(n);
    for (int i = 0; i < n; i++) suffixes[i] = order[i + 1];
    return suffixes;
}

}  // namespace detail

template <class Sequence>
std::vector<int> suffix_array(const Sequence& sequence) {
    return detail::suffix_array_impl(sequence);
}

inline std::vector<int> suffix_array(const std::string& text) {
    std::vector<unsigned char> values;
    values.reserve(text.size());
    for (unsigned char character : text) values.push_back(character);
    return detail::suffix_array_impl(values);
}

template <class Sequence>
std::vector<int> lcp_array(const Sequence& sequence, const std::vector<int>& suffixes) {
    int n = int(sequence.size());
    assert(int(suffixes.size()) == n);
    if (n == 0) return {};

    std::vector<int> rank(n);
    for (int i = 0; i < n; i++) {
        assert(0 <= suffixes[i] && suffixes[i] < n);
        rank[suffixes[i]] = i;
    }

    std::vector<int> lcp(n - 1);
    int common = 0;
    for (int i = 0; i < n; i++) {
        int position = rank[i];
        if (position == n - 1) {
            common = 0;
            continue;
        }
        int j = suffixes[position + 1];
        while (
            i + common < n &&
            j + common < n &&
            sequence[i + common] == sequence[j + common]
        ) {
            common++;
        }
        lcp[position] = common;
        if (common > 0) common--;
    }
    return lcp;
}

}  // namespace string
}  // namespace m1une


#line 11 "string/longest_common_extension.hpp"

namespace m1une {
namespace string {

template <class Sequence = std::string>
struct LongestCommonExtension {
   private:
    Sequence _sequence;
    std::vector<int> _suffix_array;
    std::vector<int> _rank;
    std::vector<int> _lcp;
    std::vector<int> _log;
    std::vector<std::vector<int>> _table;

    int range_min(int left, int right) const {
        assert(0 <= left && left < right && right <= int(_lcp.size()));
        int k = _log[right - left];
        return std::min(_table[k][left], _table[k][right - (1 << k)]);
    }

    void build() {
        int n = int(_sequence.size());
        _suffix_array = m1une::string::suffix_array(_sequence);
        _rank.assign(n, 0);
        for (int i = 0; i < n; i++) {
            _rank[_suffix_array[i]] = i;
        }

        _lcp = m1une::string::lcp_array(_sequence, _suffix_array);
        int m = int(_lcp.size());
        _log.assign(m + 1, 0);
        for (int i = 2; i <= m; i++) {
            _log[i] = _log[i >> 1] + 1;
        }

        _table.clear();
        if (m == 0) return;
        _table.assign(_log[m] + 1, std::vector<int>());
        _table[0] = _lcp;
        for (int k = 1; k < int(_table.size()); k++) {
            int width = 1 << k;
            int half = width >> 1;
            _table[k].resize(m - width + 1);
            for (int i = 0; i + width <= m; i++) {
                _table[k][i] = std::min(_table[k - 1][i], _table[k - 1][i + half]);
            }
        }
    }

   public:
    LongestCommonExtension() = default;

    explicit LongestCommonExtension(const Sequence& sequence) : _sequence(sequence) {
        build();
    }

    explicit LongestCommonExtension(Sequence&& sequence) : _sequence(std::move(sequence)) {
        build();
    }

    int size() const {
        return int(_sequence.size());
    }

    bool empty() const {
        return _sequence.empty();
    }

    const Sequence& sequence() const {
        return _sequence;
    }

    const std::vector<int>& suffix_array() const {
        return _suffix_array;
    }

    const std::vector<int>& rank() const {
        return _rank;
    }

    const std::vector<int>& lcp_array() const {
        return _lcp;
    }

    int longest_common_extension(int i, int j) const {
        int n = size();
        assert(0 <= i && i <= n);
        assert(0 <= j && j <= n);
        if (i == j) return n - i;
        if (i == n || j == n) return 0;

        int left = _rank[i];
        int right = _rank[j];
        if (left > right) std::swap(left, right);
        return range_min(left, right);
    }

    int longest_common_extension(int i, int j, int limit) const {
        assert(0 <= limit);
        return std::min(longest_common_extension(i, j), limit);
    }

    int lcp(int i, int j) const {
        return longest_common_extension(i, j);
    }

    int operator()(int i, int j) const {
        return longest_common_extension(i, j);
    }

    int compare_suffix(int i, int j) const {
        int n = size();
        assert(0 <= i && i <= n);
        assert(0 <= j && j <= n);
        if (i == j) return 0;
        int common = longest_common_extension(i, j);
        if (i + common == n && j + common == n) return 0;
        if (i + common == n) return -1;
        if (j + common == n) return 1;
        return _sequence[i + common] < _sequence[j + common] ? -1 : 1;
    }

    int compare(int l1, int r1, int l2, int r2) const {
        int n = size();
        assert(0 <= l1 && l1 <= r1 && r1 <= n);
        assert(0 <= l2 && l2 <= r2 && r2 <= n);
        int len1 = r1 - l1;
        int len2 = r2 - l2;
        int common = longest_common_extension(l1, l2, std::min(len1, len2));
        if (common == len1 && common == len2) return 0;
        if (common == len1) return -1;
        if (common == len2) return 1;
        return _sequence[l1 + common] < _sequence[l2 + common] ? -1 : 1;
    }
};

}  // namespace string
}  // namespace m1une
Back to top page