Skip to content

rolling_hash.hpp

SECTIONString INCLUDEnoya/rolling_hash.hpp

Length-aware rolling hashes modulo 2^61-1 for substring comparison, concatenation, and longest-common-extension queries.

为静态字符串或整数序列提取模 2^61-1 的带长度子串指纹,以 O(1) 比较等长子串、拼接指纹,并用二分求 LCP/LCS;适合周期与重复片段判定。

Implementation

View on GitHub

#ifndef NOYA_ROLLING_HASH_HPP
#define NOYA_ROLLING_HASH_HPP 1

/// @complexity Time: O(n) build, O(1) substring hash/equality/concatenation,
/// and O(log n) longest-common-prefix/suffix queries. Space: O(n), with powers
/// shared by rolling hashes that use the default base.

#include "noya/rnd.hpp"

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>

namespace noya {

/// @brief Arithmetic modulo the Mersenne prime 2^61-1.
struct modint61 {
  static constexpr std::uint64_t modulus = (UINT64_C(1) << 61) - 1;

  modint61() = default;

  template <class Integer,
            std::enable_if_t<std::is_integral_v<Integer>, int> = 0>
  explicit modint61(Integer value) {
    static_assert(sizeof(Integer) <= sizeof(std::uint64_t));
    if constexpr (std::is_signed_v<Integer>) {
      if (value < 0) {
        std::uint64_t magnitude = static_cast<std::uint64_t>(-(value + 1)) + 1;
        std::uint64_t remainder = reduce(magnitude);
        value_ = remainder == 0 ? 0 : modulus - remainder;
        return;
      }
    }
    value_ = reduce(static_cast<std::uint64_t>(value));
  }

  std::uint64_t val() const { return value_; }

  friend bool operator==(const modint61 &, const modint61 &) = default;

  friend modint61 operator+(modint61 left, modint61 right) {
    std::uint64_t sum = left.value_ + right.value_;
    if (sum >= modulus) {
      sum -= modulus;
    }
    return raw(sum);
  }

  friend modint61 operator-(modint61 left, modint61 right) {
    return raw(left.value_ >= right.value_
                   ? left.value_ - right.value_
                   : left.value_ + modulus - right.value_);
  }

  friend modint61 operator*(modint61 left, modint61 right) {
    __uint128_t product = static_cast<__uint128_t>(left.value_) * right.value_;
    return raw(reduce(product));
  }

private:
  std::uint64_t value_ = 0;

  static modint61 raw(std::uint64_t value) {
    modint61 result;
    result.value_ = value;
    return result;
  }

  static std::uint64_t reduce(__uint128_t value) {
    std::uint64_t folded = static_cast<std::uint64_t>(value & modulus) +
                           static_cast<std::uint64_t>(value >> 61);
    return folded >= modulus ? folded - modulus : folded;
  }
};

/// @brief Return the process-wide randomized base used by default hashes.
inline std::uint64_t rolling_hash_base() {
  constexpr std::uint64_t minimum = UINT64_C(1) << 30;
  constexpr std::uint64_t random_mask = (UINT64_C(1) << 60) - 1;
  static const std::uint64_t base =
      minimum + (internal::gen_values()() & random_mask);
  return base;
}

namespace internal {

template <class Hash> class rolling_hash_context {
public:
  explicit rolling_hash_context(std::uint64_t base) : base_(base) {
    assert(base_ != Hash(0) && base_ != Hash(1) && base_ != Hash(-1));
    powers_.emplace_back(1);
  }

  const Hash &base() const { return base_; }

  const Hash &power(int exponent) {
    assert(exponent >= 0);
    while (int(powers_.size()) <= exponent) {
      powers_.push_back(powers_.back() * base_);
    }
    return powers_[exponent];
  }

private:
  Hash base_;
  std::vector<Hash> powers_;
};

template <class Hash>
std::shared_ptr<rolling_hash_context<Hash>> default_rolling_hash_context() {
  static auto context =
      std::make_shared<rolling_hash_context<Hash>>(rolling_hash_base());
  return context;
}

template <class T> auto normalize_hash_symbol(const T &value) {
  using raw_type = std::remove_cv_t<T>;
  if constexpr (std::is_same_v<raw_type, char> ||
                std::is_same_v<raw_type, signed char> ||
                std::is_same_v<raw_type, unsigned char>) {
    return static_cast<unsigned char>(value);
  } else {
    return value;
  }
}

template <class Hash> int hash_size(const Hash &hash) {
  if constexpr (requires { hash.size(); }) {
    return hash.size();
  } else {
    return hash.n;
  }
}

template <class First, class Second>
void assert_compatible_hashes(const First &first, const Second &second) {
  if constexpr (requires { first.compatible_with(second); }) {
    assert(first.compatible_with(second));
  }
}

} // namespace internal

/// @brief A substring hash token that also records its sequence length.
template <class Hash> struct rolling_hash_value {
  Hash hash{};
  int length = 0;

  friend bool operator==(const rolling_hash_value &,
                         const rolling_hash_value &) = default;
};

/// @brief Prefix rolling hash with length-aware substring and concatenation
/// tokens. The default modulus is the Mersenne prime 2^61-1.
/// @details Prefixes satisfy H[i + 1] = H[i] * base + value[i], hence
/// hash([l, r)) = H[r] - H[l] * base^(r-l). A token stores its length so hashes
/// concatenate as left * base^(right.length) + right without ambiguity between
/// equal numeric hashes of different lengths.
template <class Symbol, class Hash = modint61> class rolling_hash {
public:
  using symbol_type = Symbol;
  using hash_type = Hash;
  using value_type = rolling_hash_value<Hash>;

  rolling_hash() : context_(internal::default_rolling_hash_context<Hash>()) {
    prefix_.emplace_back();
  }

  explicit rolling_hash(std::uint64_t base)
      : context_(std::make_shared<context_type>(base)), prefix_(1) {}

  explicit rolling_hash(const std::vector<Symbol> &values) : rolling_hash() {
    build(values);
  }

  rolling_hash(const std::vector<Symbol> &values, std::uint64_t base)
      : rolling_hash(base) {
    build(values);
  }

  explicit rolling_hash(const std::string &values) : rolling_hash() {
    build(values);
  }

  rolling_hash(const std::string &values, std::uint64_t base)
      : rolling_hash(base) {
    build(values);
  }

  void build(const std::vector<Symbol> &values) {
    build(values.begin(), values.end());
  }

  void build(const std::string &values) { build(values.begin(), values.end()); }

  int size() const { return int(prefix_.size()) - 1; }
  bool empty() const { return size() == 0; }
  const Hash &base() const { return context_->base(); }

  template <class OtherSymbol>
  bool compatible_with(const rolling_hash<OtherSymbol, Hash> &other) const {
    return base() == other.base();
  }

  /// @brief Return a length-aware hash of [left, right).
  value_type prod(int left, int right) const {
    assert(0 <= left && left <= right && right <= size());
    int length = right - left;
    return {prefix_[right] - prefix_[left] * context_->power(length), length};
  }

  value_type slice(int left, int right) const { return prod(left, right); }

  /// @brief Return the hash token of left followed by right. Both tokens must
  /// have been computed with this object's base.
  value_type concat(const value_type &left, const value_type &right) const {
    assert(left.length >= 0 && right.length >= 0);
    return {left.hash * context_->power(right.length) + right.hash,
            left.length + right.length};
  }

private:
  using context_type = internal::rolling_hash_context<Hash>;
  std::shared_ptr<context_type> context_;
  std::vector<Hash> prefix_;

  template <class Iterator> void build(Iterator first, Iterator last) {
    prefix_.assign(1, Hash{});
    for (; first != last; ++first) {
      Hash symbol(internal::normalize_hash_symbol(Symbol(*first)));
      prefix_.push_back(prefix_.back() * base() + symbol);
    }
    context_->power(size());
  }
};

/// @brief Longest common prefix of suffixes beginning at first_index and
/// second_index.
template <class FirstHash, class SecondHash>
int lcp(const FirstHash &first, int first_index, const SecondHash &second,
        int second_index) {
  int first_size = internal::hash_size(first);
  int second_size = internal::hash_size(second);
  assert(0 <= first_index && first_index <= first_size);
  assert(0 <= second_index && second_index <= second_size);
  internal::assert_compatible_hashes(first, second);

  int good = 0;
  int bad = std::min(first_size - first_index, second_size - second_index) + 1;
  while (bad - good > 1) {
    int middle = (good + bad) / 2;
    if (first.prod(first_index, first_index + middle) ==
        second.prod(second_index, second_index + middle)) {
      good = middle;
    } else {
      bad = middle;
    }
  }
  return good;
}

/// @brief Longest common suffix ending at first_index and second_index. Index
/// -1 denotes the empty prefix before a sequence.
template <class FirstHash, class SecondHash>
int lcs(const FirstHash &first, int first_index, const SecondHash &second,
        int second_index) {
  int first_size = internal::hash_size(first);
  int second_size = internal::hash_size(second);
  assert(-1 <= first_index && first_index < first_size);
  assert(-1 <= second_index && second_index < second_size);
  internal::assert_compatible_hashes(first, second);

  int good = 0;
  int bad = std::min(first_index + 1, second_index + 1) + 1;
  while (bad - good > 1) {
    int middle = (good + bad) / 2;
    if (first.prod(first_index + 1 - middle, first_index + 1) ==
        second.prod(second_index + 1 - middle, second_index + 1)) {
      good = middle;
    } else {
      bad = middle;
    }
  }
  return good;
}

/// @brief Check whether [first_left, first_right) equals
/// [second_left, second_right) in O(1).
template <class FirstHash, class SecondHash>
bool same(const FirstHash &first, int first_left, int first_right,
          const SecondHash &second, int second_left, int second_right) {
  int first_size = internal::hash_size(first);
  int second_size = internal::hash_size(second);
  assert(0 <= first_left && first_left <= first_right &&
         first_right <= first_size);
  assert(0 <= second_left && second_left <= second_right &&
         second_right <= second_size);
  internal::assert_compatible_hashes(first, second);
  return first_right - first_left == second_right - second_left &&
         first.prod(first_left, first_right) ==
             second.prod(second_left, second_right);
}

} // namespace noya

#endif // NOYA_ROLLING_HASH_HPP
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <ctime>
#include <memory>
#include <numeric>
#include <random>
#include <string>
#include <type_traits>
#include <vector>

/// @complexity Time: O(n) build, O(1) substring hash/equality/concatenation,
/// and O(log n) longest-common-prefix/suffix queries. Space: O(n), with powers
/// shared by rolling hashes that use the default base.

/// @complexity Time: O(n) per generated permutation/tree; O(1) scalar draws.
/// Space: O(n) returned data.

/// @brief Random permutation, hash-value, and rooted-tree generators backed by
/// a shared 64-bit Mersenne Twister.

namespace noya {
using ull = unsigned long long;
namespace internal {
inline std::mt19937_64 &gen_values() {
  static std::mt19937_64 gen(time(0));
  return gen;
}
} // namespace internal

inline std::vector<int> random_permutation(int N) {
  std::vector<int> p(N);
  std::iota(p.begin(), p.end(), 0);
  std::shuffle(p.begin(), p.end(), internal::gen_values());
  return p;
}
inline std::vector<ull> random_hash_values(int N) {
  std::vector<ull> X(N);
  std::generate(X.begin(), X.end(), internal::gen_values());
  return X;
}
inline std::vector<std::vector<int>> random_tree(int N) {
  std::vector<std::vector<int>> g(N);
  for (int i = 1; i < N; i++) {
    int p = internal::gen_values()() % i;
    g[p].push_back(i);
  }
  return g;
}
} // namespace noya

namespace noya {

/// @brief Arithmetic modulo the Mersenne prime 2^61-1.
struct modint61 {
  static constexpr std::uint64_t modulus = (UINT64_C(1) << 61) - 1;

  modint61() = default;

  template <class Integer,
            std::enable_if_t<std::is_integral_v<Integer>, int> = 0>
  explicit modint61(Integer value) {
    static_assert(sizeof(Integer) <= sizeof(std::uint64_t));
    if constexpr (std::is_signed_v<Integer>) {
      if (value < 0) {
        std::uint64_t magnitude = static_cast<std::uint64_t>(-(value + 1)) + 1;
        std::uint64_t remainder = reduce(magnitude);
        value_ = remainder == 0 ? 0 : modulus - remainder;
        return;
      }
    }
    value_ = reduce(static_cast<std::uint64_t>(value));
  }

  std::uint64_t val() const { return value_; }

  friend bool operator==(const modint61 &, const modint61 &) = default;

  friend modint61 operator+(modint61 left, modint61 right) {
    std::uint64_t sum = left.value_ + right.value_;
    if (sum >= modulus) {
      sum -= modulus;
    }
    return raw(sum);
  }

  friend modint61 operator-(modint61 left, modint61 right) {
    return raw(left.value_ >= right.value_
                   ? left.value_ - right.value_
                   : left.value_ + modulus - right.value_);
  }

  friend modint61 operator*(modint61 left, modint61 right) {
    __uint128_t product = static_cast<__uint128_t>(left.value_) * right.value_;
    return raw(reduce(product));
  }

private:
  std::uint64_t value_ = 0;

  static modint61 raw(std::uint64_t value) {
    modint61 result;
    result.value_ = value;
    return result;
  }

  static std::uint64_t reduce(__uint128_t value) {
    std::uint64_t folded = static_cast<std::uint64_t>(value & modulus) +
                           static_cast<std::uint64_t>(value >> 61);
    return folded >= modulus ? folded - modulus : folded;
  }
};

/// @brief Return the process-wide randomized base used by default hashes.
inline std::uint64_t rolling_hash_base() {
  constexpr std::uint64_t minimum = UINT64_C(1) << 30;
  constexpr std::uint64_t random_mask = (UINT64_C(1) << 60) - 1;
  static const std::uint64_t base =
      minimum + (internal::gen_values()() & random_mask);
  return base;
}

namespace internal {

template <class Hash> class rolling_hash_context {
public:
  explicit rolling_hash_context(std::uint64_t base) : base_(base) {
    assert(base_ != Hash(0) && base_ != Hash(1) && base_ != Hash(-1));
    powers_.emplace_back(1);
  }

  const Hash &base() const { return base_; }

  const Hash &power(int exponent) {
    assert(exponent >= 0);
    while (int(powers_.size()) <= exponent) {
      powers_.push_back(powers_.back() * base_);
    }
    return powers_[exponent];
  }

private:
  Hash base_;
  std::vector<Hash> powers_;
};

template <class Hash>
std::shared_ptr<rolling_hash_context<Hash>> default_rolling_hash_context() {
  static auto context =
      std::make_shared<rolling_hash_context<Hash>>(rolling_hash_base());
  return context;
}

template <class T> auto normalize_hash_symbol(const T &value) {
  using raw_type = std::remove_cv_t<T>;
  if constexpr (std::is_same_v<raw_type, char> ||
                std::is_same_v<raw_type, signed char> ||
                std::is_same_v<raw_type, unsigned char>) {
    return static_cast<unsigned char>(value);
  } else {
    return value;
  }
}

template <class Hash> int hash_size(const Hash &hash) {
  if constexpr (requires { hash.size(); }) {
    return hash.size();
  } else {
    return hash.n;
  }
}

template <class First, class Second>
void assert_compatible_hashes(const First &first, const Second &second) {
  if constexpr (requires { first.compatible_with(second); }) {
    assert(first.compatible_with(second));
  }
}

} // namespace internal

/// @brief A substring hash token that also records its sequence length.
template <class Hash> struct rolling_hash_value {
  Hash hash{};
  int length = 0;

  friend bool operator==(const rolling_hash_value &,
                         const rolling_hash_value &) = default;
};

/// @brief Prefix rolling hash with length-aware substring and concatenation
/// tokens. The default modulus is the Mersenne prime 2^61-1.
/// @details Prefixes satisfy H[i + 1] = H[i] * base + value[i], hence
/// hash([l, r)) = H[r] - H[l] * base^(r-l). A token stores its length so hashes
/// concatenate as left * base^(right.length) + right without ambiguity between
/// equal numeric hashes of different lengths.
template <class Symbol, class Hash = modint61> class rolling_hash {
public:
  using symbol_type = Symbol;
  using hash_type = Hash;
  using value_type = rolling_hash_value<Hash>;

  rolling_hash() : context_(internal::default_rolling_hash_context<Hash>()) {
    prefix_.emplace_back();
  }

  explicit rolling_hash(std::uint64_t base)
      : context_(std::make_shared<context_type>(base)), prefix_(1) {}

  explicit rolling_hash(const std::vector<Symbol> &values) : rolling_hash() {
    build(values);
  }

  rolling_hash(const std::vector<Symbol> &values, std::uint64_t base)
      : rolling_hash(base) {
    build(values);
  }

  explicit rolling_hash(const std::string &values) : rolling_hash() {
    build(values);
  }

  rolling_hash(const std::string &values, std::uint64_t base)
      : rolling_hash(base) {
    build(values);
  }

  void build(const std::vector<Symbol> &values) {
    build(values.begin(), values.end());
  }

  void build(const std::string &values) { build(values.begin(), values.end()); }

  int size() const { return int(prefix_.size()) - 1; }
  bool empty() const { return size() == 0; }
  const Hash &base() const { return context_->base(); }

  template <class OtherSymbol>
  bool compatible_with(const rolling_hash<OtherSymbol, Hash> &other) const {
    return base() == other.base();
  }

  /// @brief Return a length-aware hash of [left, right).
  value_type prod(int left, int right) const {
    assert(0 <= left && left <= right && right <= size());
    int length = right - left;
    return {prefix_[right] - prefix_[left] * context_->power(length), length};
  }

  value_type slice(int left, int right) const { return prod(left, right); }

  /// @brief Return the hash token of left followed by right. Both tokens must
  /// have been computed with this object's base.
  value_type concat(const value_type &left, const value_type &right) const {
    assert(left.length >= 0 && right.length >= 0);
    return {left.hash * context_->power(right.length) + right.hash,
            left.length + right.length};
  }

private:
  using context_type = internal::rolling_hash_context<Hash>;
  std::shared_ptr<context_type> context_;
  std::vector<Hash> prefix_;

  template <class Iterator> void build(Iterator first, Iterator last) {
    prefix_.assign(1, Hash{});
    for (; first != last; ++first) {
      Hash symbol(internal::normalize_hash_symbol(Symbol(*first)));
      prefix_.push_back(prefix_.back() * base() + symbol);
    }
    context_->power(size());
  }
};

/// @brief Longest common prefix of suffixes beginning at first_index and
/// second_index.
template <class FirstHash, class SecondHash>
int lcp(const FirstHash &first, int first_index, const SecondHash &second,
        int second_index) {
  int first_size = internal::hash_size(first);
  int second_size = internal::hash_size(second);
  assert(0 <= first_index && first_index <= first_size);
  assert(0 <= second_index && second_index <= second_size);
  internal::assert_compatible_hashes(first, second);

  int good = 0;
  int bad = std::min(first_size - first_index, second_size - second_index) + 1;
  while (bad - good > 1) {
    int middle = (good + bad) / 2;
    if (first.prod(first_index, first_index + middle) ==
        second.prod(second_index, second_index + middle)) {
      good = middle;
    } else {
      bad = middle;
    }
  }
  return good;
}

/// @brief Longest common suffix ending at first_index and second_index. Index
/// -1 denotes the empty prefix before a sequence.
template <class FirstHash, class SecondHash>
int lcs(const FirstHash &first, int first_index, const SecondHash &second,
        int second_index) {
  int first_size = internal::hash_size(first);
  int second_size = internal::hash_size(second);
  assert(-1 <= first_index && first_index < first_size);
  assert(-1 <= second_index && second_index < second_size);
  internal::assert_compatible_hashes(first, second);

  int good = 0;
  int bad = std::min(first_index + 1, second_index + 1) + 1;
  while (bad - good > 1) {
    int middle = (good + bad) / 2;
    if (first.prod(first_index + 1 - middle, first_index + 1) ==
        second.prod(second_index + 1 - middle, second_index + 1)) {
      good = middle;
    } else {
      bad = middle;
    }
  }
  return good;
}

/// @brief Check whether [first_left, first_right) equals
/// [second_left, second_right) in O(1).
template <class FirstHash, class SecondHash>
bool same(const FirstHash &first, int first_left, int first_right,
          const SecondHash &second, int second_left, int second_right) {
  int first_size = internal::hash_size(first);
  int second_size = internal::hash_size(second);
  assert(0 <= first_left && first_left <= first_right &&
         first_right <= first_size);
  assert(0 <= second_left && second_left <= second_right &&
         second_right <= second_size);
  internal::assert_compatible_hashes(first, second);
  return first_right - first_left == second_right - second_left &&
         first.prod(first_left, first_right) ==
             second.prod(second_left, second_right);
}

} // namespace noya