Skip to content

deque_palindromic_tree.hpp

SECTIONString INCLUDEnoya/deque_palindromic_tree.hpp

支持在字符串两端加入字符,并维护不同回文子串与最长回文信息。

Complexity: Time: push O(log n + log sigma) worst case, pop O(log sigma) amortized, and query O(1). Space: O(n), where n is the current length and sigma is the number of distinct ch values.

AC 记录:palindromes_in_deque

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include

/// @complexity Time: push O(log n + log sigma) worst case, pop O(log sigma)
/// amortized, and query O(1). Space: O(n), where n is the current length and
/// sigma is the number of distinct ch values.

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <functional>
#include <iterator>
#include <map>
#include <memory>
#include <utility>
#include <vector>

namespace noya {

namespace deque_palindromic_tree_internal {

template <class T> class amortized_deque {
  std::vector<T> l_, r_;

  static void move_half(std::vector<T> &t, std::vector<T> &s) {
    assert(t.empty() && !s.empty());
    std::size_t mov = (s.size() + 1) / 2;
    std::move(s.rend() - mov, s.rend(), std::back_inserter(t));
    s.erase(s.begin(), s.begin() + mov);
  }

public:
  int size() const { return int(l_.size() + r_.size()); }
  bool empty() const { return l_.empty() && r_.empty(); }

  T &operator[](int i) {
    assert(0 <= i && i < size());
    return i < int(l_.size()) ? l_[l_.size() - 1 - std::size_t(i)]
                              : r_[std::size_t(i) - l_.size()];
  }

  const T &operator[](int i) const {
    assert(0 <= i && i < size());
    return i < int(l_.size()) ? l_[l_.size() - 1 - std::size_t(i)]
                              : r_[std::size_t(i) - l_.size()];
  }

  T &front() { return l_.empty() ? r_.front() : l_.back(); }
  const T &front() const { return l_.empty() ? r_.front() : l_.back(); }
  T &back() { return r_.empty() ? l_.front() : r_.back(); }
  const T &back() const { return r_.empty() ? l_.front() : r_.back(); }

  void push_front(const T &val) { l_.push_back(val); }
  void push_front(T &&val) { l_.push_back(std::move(val)); }
  void push_back(const T &val) { r_.push_back(val); }
  void push_back(T &&val) { r_.push_back(std::move(val)); }

  void pop_front() {
    assert(!empty());
    if (l_.empty()) {
      move_half(l_, r_);
    }
    l_.pop_back();
  }

  void pop_back() {
    assert(!empty());
    if (r_.empty()) {
      move_half(r_, l_);
    }
    r_.pop_back();
  }
};

} // namespace deque_palindromic_tree_internal

/// @brief Maintain all distinct palindromic substrings under pushes and pops
/// at both ends. Every active palindrome is a node with a suffix link, while
/// each text position stores the longest palindrome beginning or ending there.
/// A quick link skips suffix-link runs whose next extension character is the
/// same, so a new extendable boundary palindrome is found logarithmically.
/// Surface counters and incoming suffix-link counters identify a node exactly
/// when its last occurrence disappears, allowing an end deletion to undo only
/// local certificates. Two reversed vectors provide amortized constant-time
/// deque rebalancing while retaining indexed ch access.
template <class Char = int, class Compare = std::less<Char>>
class deque_palindromic_tree {
  struct node {
    node *fa = nullptr;
    node *lnk = nullptr;
    node *ql = nullptr;
    std::map<Char, node *, Compare> nxt;
    int len = 0;
    int cnt = 0;
    int il = 0;
  };

  struct deque_entry {
    Char ch;
    node *ps;
    node *ss;
  };

  using text_deque =
      deque_palindromic_tree_internal::amortized_deque<deque_entry>;

  text_deque s_;
  std::unique_ptr<node> odd, er;
  int dn = 0;

  node *back_appendable(const Char &ch, node *cur) {
    int n = s_.size();
    while (true) {
      if (cur->len == -1 || (cur->len < n && s_[n - cur->len - 1].ch == ch)) {
        return cur;
      }
      node *suf = cur->lnk;
      if (suf->len == -1 || s_[n - suf->len - 1].ch == ch) {
        return suf;
      }
      cur = cur->ql;
    }
  }

  node *front_appendable(const Char &ch, node *cur) {
    int n = s_.size();
    while (true) {
      if (cur->len == -1 || (cur->len < n && s_[cur->len].ch == ch)) {
        return cur;
      }
      node *suf = cur->lnk;
      if (suf->len == -1 || s_[suf->len].ch == ch) {
        return suf;
      }
      cur = cur->ql;
    }
  }

  static node *transition(node *u, const Char &ch) {
    auto it = u->nxt.find(ch);
    assert(it != u->nxt.end());
    return it->second;
  }

public:
  deque_palindromic_tree()
      : odd(std::make_unique<node>()), er(std::make_unique<node>()) {
    odd->len = -1;
    odd->fa = odd->lnk = odd->ql = odd.get();
    er->len = 0;
    er->fa = er->lnk = er->ql = odd.get();
  }

  deque_palindromic_tree(const deque_palindromic_tree &) = delete;
  deque_palindromic_tree &operator=(const deque_palindromic_tree &) = delete;
  deque_palindromic_tree(deque_palindromic_tree &&) = delete;
  deque_palindromic_tree &operator=(deque_palindromic_tree &&) = delete;

  ~deque_palindromic_tree() { clear(); }

  int size() const { return s_.size(); }
  bool empty() const { return s_.empty(); }
  int distinct_palindromes() const { return dn; }
  int longest_prefix_palindrome() const {
    return empty() ? 0 : s_.front().ps->len;
  }
  int longest_suffix_palindrome() const {
    return empty() ? 0 : s_.back().ss->len;
  }

  void push_back(const Char &ch) {
    node *fa = empty() ? odd.get() : back_appendable(ch, s_.back().ss);
    int n = size();
    node *crt = nullptr;
    node *suf = er.get();
    auto old = fa->nxt.find(ch);
    if (old == fa->nxt.end()) {
      crt = new node();
      crt->fa = fa;
      crt->len = fa->len + 2;
      if (fa != odd.get()) {
        node *sp = back_appendable(ch, fa->lnk);
        suf = transition(sp, ch);
      }
      crt->lnk = suf;
      suf->il++;
      s_.push_back({ch, er.get(), er.get()});
      n++;
      if (suf->lnk != odd.get() &&
          s_[n - suf->len - 1].ch == s_[n - suf->lnk->len - 1].ch) {
        crt->ql = suf->ql;
      } else {
        crt->ql = suf->lnk;
      }
      fa->nxt.emplace(ch, crt);
      dn++;
    } else {
      s_.push_back({ch, er.get(), er.get()});
      n++;
      crt = old->second;
      suf = crt->lnk;
    }
    s_[n - 1].ss = crt;
    s_[n - crt->len].ps = crt;
    if (suf->len >= 1 && s_[n - crt->len + suf->len - 1].ss == suf) {
      s_[n - crt->len + suf->len - 1].ss = er.get();
    }
    crt->cnt++;
  }

  void push_front(const Char &ch) {
    node *fa = empty() ? odd.get() : front_appendable(ch, s_.front().ps);
    node *crt = nullptr;
    node *suf = er.get();
    auto old = fa->nxt.find(ch);
    if (old == fa->nxt.end()) {
      crt = new node();
      crt->fa = fa;
      crt->len = fa->len + 2;
      if (fa != odd.get()) {
        node *sp = front_appendable(ch, fa->lnk);
        suf = transition(sp, ch);
      }
      crt->lnk = suf;
      suf->il++;
      s_.push_front({ch, er.get(), er.get()});
      if (suf->lnk != odd.get() && s_[suf->len].ch == s_[suf->lnk->len].ch) {
        crt->ql = suf->ql;
      } else {
        crt->ql = suf->lnk;
      }
      fa->nxt.emplace(ch, crt);
      dn++;
    } else {
      s_.push_front({ch, er.get(), er.get()});
      crt = old->second;
      suf = crt->lnk;
    }
    s_[0].ps = crt;
    s_[crt->len - 1].ss = crt;
    if (suf->len >= 1 && s_[crt->len - suf->len].ps == suf) {
      s_[crt->len - suf->len].ps = er.get();
    }
    crt->cnt++;
  }

  void pop_back() {
    assert(!empty());
    node *del = s_.back().ss;
    Char ch = s_.back().ch;
    node *suf = del->lnk;
    int n = size();
    if (del->len >= 2 && s_[n - del->len + suf->len - 1].ss->len < suf->len) {
      s_[n - del->len + suf->len - 1].ss = suf;
      s_[n - del->len].ps = suf;
    } else {
      s_[n - del->len].ps = er.get();
    }
    del->cnt--;
    if (del->il == 0 && del->cnt == 0) {
      std::size_t de1 = del->fa->nxt.erase(ch);
      assert(de1 == 1);
      suf->il--;
      delete del;
      dn--;
    }
    s_.pop_back();
  }

  void pop_front() {
    assert(!empty());
    node *del = s_.front().ps;
    Char ch = s_.front().ch;
    node *suf = del->lnk;
    if (del->len >= 2 && s_[del->len - suf->len].ps->len < suf->len) {
      s_[del->len - suf->len].ps = suf;
      s_[del->len - 1].ss = suf;
    } else {
      s_[del->len - 1].ss = er.get();
    }
    del->cnt--;
    if (del->il == 0 && del->cnt == 0) {
      std::size_t de1 = del->fa->nxt.erase(ch);
      assert(de1 == 1);
      suf->il--;
      delete del;
      dn--;
    }
    s_.pop_front();
  }

  void clear() {
    while (!empty()) {
      pop_back();
    }
  }
};

} // namespace noya
#ifndef NOYA_DEQUE_PALINDROMIC_TREE_HPP
#define NOYA_DEQUE_PALINDROMIC_TREE_HPP 1

/// @complexity Time: push O(log n + log sigma) worst case, pop O(log sigma)
/// amortized, and query O(1). Space: O(n), where n is the current length and
/// sigma is the number of distinct ch values.

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <functional>
#include <iterator>
#include <map>
#include <memory>
#include <utility>
#include <vector>

namespace noya {

namespace deque_palindromic_tree_internal {

template <class T> class amortized_deque {
  std::vector<T> l_, r_;

  static void move_half(std::vector<T> &t, std::vector<T> &s) {
    assert(t.empty() && !s.empty());
    std::size_t mov = (s.size() + 1) / 2;
    std::move(s.rend() - mov, s.rend(), std::back_inserter(t));
    s.erase(s.begin(), s.begin() + mov);
  }

public:
  int size() const { return int(l_.size() + r_.size()); }
  bool empty() const { return l_.empty() && r_.empty(); }

  T &operator[](int i) {
    assert(0 <= i && i < size());
    return i < int(l_.size()) ? l_[l_.size() - 1 - std::size_t(i)]
                              : r_[std::size_t(i) - l_.size()];
  }

  const T &operator[](int i) const {
    assert(0 <= i && i < size());
    return i < int(l_.size()) ? l_[l_.size() - 1 - std::size_t(i)]
                              : r_[std::size_t(i) - l_.size()];
  }

  T &front() { return l_.empty() ? r_.front() : l_.back(); }
  const T &front() const { return l_.empty() ? r_.front() : l_.back(); }
  T &back() { return r_.empty() ? l_.front() : r_.back(); }
  const T &back() const { return r_.empty() ? l_.front() : r_.back(); }

  void push_front(const T &val) { l_.push_back(val); }
  void push_front(T &&val) { l_.push_back(std::move(val)); }
  void push_back(const T &val) { r_.push_back(val); }
  void push_back(T &&val) { r_.push_back(std::move(val)); }

  void pop_front() {
    assert(!empty());
    if (l_.empty()) {
      move_half(l_, r_);
    }
    l_.pop_back();
  }

  void pop_back() {
    assert(!empty());
    if (r_.empty()) {
      move_half(r_, l_);
    }
    r_.pop_back();
  }
};

} // namespace deque_palindromic_tree_internal

/// @brief Maintain all distinct palindromic substrings under pushes and pops
/// at both ends. Every active palindrome is a node with a suffix link, while
/// each text position stores the longest palindrome beginning or ending there.
/// A quick link skips suffix-link runs whose next extension character is the
/// same, so a new extendable boundary palindrome is found logarithmically.
/// Surface counters and incoming suffix-link counters identify a node exactly
/// when its last occurrence disappears, allowing an end deletion to undo only
/// local certificates. Two reversed vectors provide amortized constant-time
/// deque rebalancing while retaining indexed ch access.
template <class Char = int, class Compare = std::less<Char>>
class deque_palindromic_tree {
  struct node {
    node *fa = nullptr;
    node *lnk = nullptr;
    node *ql = nullptr;
    std::map<Char, node *, Compare> nxt;
    int len = 0;
    int cnt = 0;
    int il = 0;
  };

  struct deque_entry {
    Char ch;
    node *ps;
    node *ss;
  };

  using text_deque =
      deque_palindromic_tree_internal::amortized_deque<deque_entry>;

  text_deque s_;
  std::unique_ptr<node> odd, er;
  int dn = 0;

  node *back_appendable(const Char &ch, node *cur) {
    int n = s_.size();
    while (true) {
      if (cur->len == -1 || (cur->len < n && s_[n - cur->len - 1].ch == ch)) {
        return cur;
      }
      node *suf = cur->lnk;
      if (suf->len == -1 || s_[n - suf->len - 1].ch == ch) {
        return suf;
      }
      cur = cur->ql;
    }
  }

  node *front_appendable(const Char &ch, node *cur) {
    int n = s_.size();
    while (true) {
      if (cur->len == -1 || (cur->len < n && s_[cur->len].ch == ch)) {
        return cur;
      }
      node *suf = cur->lnk;
      if (suf->len == -1 || s_[suf->len].ch == ch) {
        return suf;
      }
      cur = cur->ql;
    }
  }

  static node *transition(node *u, const Char &ch) {
    auto it = u->nxt.find(ch);
    assert(it != u->nxt.end());
    return it->second;
  }

public:
  deque_palindromic_tree()
      : odd(std::make_unique<node>()), er(std::make_unique<node>()) {
    odd->len = -1;
    odd->fa = odd->lnk = odd->ql = odd.get();
    er->len = 0;
    er->fa = er->lnk = er->ql = odd.get();
  }

  deque_palindromic_tree(const deque_palindromic_tree &) = delete;
  deque_palindromic_tree &operator=(const deque_palindromic_tree &) = delete;
  deque_palindromic_tree(deque_palindromic_tree &&) = delete;
  deque_palindromic_tree &operator=(deque_palindromic_tree &&) = delete;

  ~deque_palindromic_tree() { clear(); }

  int size() const { return s_.size(); }
  bool empty() const { return s_.empty(); }
  int distinct_palindromes() const { return dn; }
  int longest_prefix_palindrome() const {
    return empty() ? 0 : s_.front().ps->len;
  }
  int longest_suffix_palindrome() const {
    return empty() ? 0 : s_.back().ss->len;
  }

  void push_back(const Char &ch) {
    node *fa = empty() ? odd.get() : back_appendable(ch, s_.back().ss);
    int n = size();
    node *crt = nullptr;
    node *suf = er.get();
    auto old = fa->nxt.find(ch);
    if (old == fa->nxt.end()) {
      crt = new node();
      crt->fa = fa;
      crt->len = fa->len + 2;
      if (fa != odd.get()) {
        node *sp = back_appendable(ch, fa->lnk);
        suf = transition(sp, ch);
      }
      crt->lnk = suf;
      suf->il++;
      s_.push_back({ch, er.get(), er.get()});
      n++;
      if (suf->lnk != odd.get() &&
          s_[n - suf->len - 1].ch == s_[n - suf->lnk->len - 1].ch) {
        crt->ql = suf->ql;
      } else {
        crt->ql = suf->lnk;
      }
      fa->nxt.emplace(ch, crt);
      dn++;
    } else {
      s_.push_back({ch, er.get(), er.get()});
      n++;
      crt = old->second;
      suf = crt->lnk;
    }
    s_[n - 1].ss = crt;
    s_[n - crt->len].ps = crt;
    if (suf->len >= 1 && s_[n - crt->len + suf->len - 1].ss == suf) {
      s_[n - crt->len + suf->len - 1].ss = er.get();
    }
    crt->cnt++;
  }

  void push_front(const Char &ch) {
    node *fa = empty() ? odd.get() : front_appendable(ch, s_.front().ps);
    node *crt = nullptr;
    node *suf = er.get();
    auto old = fa->nxt.find(ch);
    if (old == fa->nxt.end()) {
      crt = new node();
      crt->fa = fa;
      crt->len = fa->len + 2;
      if (fa != odd.get()) {
        node *sp = front_appendable(ch, fa->lnk);
        suf = transition(sp, ch);
      }
      crt->lnk = suf;
      suf->il++;
      s_.push_front({ch, er.get(), er.get()});
      if (suf->lnk != odd.get() && s_[suf->len].ch == s_[suf->lnk->len].ch) {
        crt->ql = suf->ql;
      } else {
        crt->ql = suf->lnk;
      }
      fa->nxt.emplace(ch, crt);
      dn++;
    } else {
      s_.push_front({ch, er.get(), er.get()});
      crt = old->second;
      suf = crt->lnk;
    }
    s_[0].ps = crt;
    s_[crt->len - 1].ss = crt;
    if (suf->len >= 1 && s_[crt->len - suf->len].ps == suf) {
      s_[crt->len - suf->len].ps = er.get();
    }
    crt->cnt++;
  }

  void pop_back() {
    assert(!empty());
    node *del = s_.back().ss;
    Char ch = s_.back().ch;
    node *suf = del->lnk;
    int n = size();
    if (del->len >= 2 && s_[n - del->len + suf->len - 1].ss->len < suf->len) {
      s_[n - del->len + suf->len - 1].ss = suf;
      s_[n - del->len].ps = suf;
    } else {
      s_[n - del->len].ps = er.get();
    }
    del->cnt--;
    if (del->il == 0 && del->cnt == 0) {
      std::size_t de1 = del->fa->nxt.erase(ch);
      assert(de1 == 1);
      suf->il--;
      delete del;
      dn--;
    }
    s_.pop_back();
  }

  void pop_front() {
    assert(!empty());
    node *del = s_.front().ps;
    Char ch = s_.front().ch;
    node *suf = del->lnk;
    if (del->len >= 2 && s_[del->len - suf->len].ps->len < suf->len) {
      s_[del->len - suf->len].ps = suf;
      s_[del->len - 1].ss = suf;
    } else {
      s_[del->len - 1].ss = er.get();
    }
    del->cnt--;
    if (del->il == 0 && del->cnt == 0) {
      std::size_t de1 = del->fa->nxt.erase(ch);
      assert(de1 == 1);
      suf->il--;
      delete del;
      dn--;
    }
    s_.pop_front();
  }

  void clear() {
    while (!empty()) {
      pop_back();
    }
  }
};

} // namespace noya

#endif // NOYA_DEQUE_PALINDROMIC_TREE_HPP
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <functional>
#include <iterator>
#include <map>
#include <memory>
#include <utility>
#include <vector>

/// @complexity Time: push O(log n + log sigma) worst case, pop O(log sigma)
/// amortized, and query O(1). Space: O(n), where n is the current length and
/// sigma is the number of distinct ch values.

namespace noya {

namespace deque_palindromic_tree_internal {

template <class T> class amortized_deque {
  std::vector<T> l_, r_;

  static void move_half(std::vector<T> &t, std::vector<T> &s) {
    assert(t.empty() && !s.empty());
    std::size_t mov = (s.size() + 1) / 2;
    std::move(s.rend() - mov, s.rend(), std::back_inserter(t));
    s.erase(s.begin(), s.begin() + mov);
  }

public:
  int size() const { return int(l_.size() + r_.size()); }
  bool empty() const { return l_.empty() && r_.empty(); }

  T &operator[](int i) {
    assert(0 <= i && i < size());
    return i < int(l_.size()) ? l_[l_.size() - 1 - std::size_t(i)]
                              : r_[std::size_t(i) - l_.size()];
  }

  const T &operator[](int i) const {
    assert(0 <= i && i < size());
    return i < int(l_.size()) ? l_[l_.size() - 1 - std::size_t(i)]
                              : r_[std::size_t(i) - l_.size()];
  }

  T &front() { return l_.empty() ? r_.front() : l_.back(); }
  const T &front() const { return l_.empty() ? r_.front() : l_.back(); }
  T &back() { return r_.empty() ? l_.front() : r_.back(); }
  const T &back() const { return r_.empty() ? l_.front() : r_.back(); }

  void push_front(const T &val) { l_.push_back(val); }
  void push_front(T &&val) { l_.push_back(std::move(val)); }
  void push_back(const T &val) { r_.push_back(val); }
  void push_back(T &&val) { r_.push_back(std::move(val)); }

  void pop_front() {
    assert(!empty());
    if (l_.empty()) {
      move_half(l_, r_);
    }
    l_.pop_back();
  }

  void pop_back() {
    assert(!empty());
    if (r_.empty()) {
      move_half(r_, l_);
    }
    r_.pop_back();
  }
};

} // namespace deque_palindromic_tree_internal

/// @brief Maintain all distinct palindromic substrings under pushes and pops
/// at both ends. Every active palindrome is a node with a suffix link, while
/// each text position stores the longest palindrome beginning or ending there.
/// A quick link skips suffix-link runs whose next extension character is the
/// same, so a new extendable boundary palindrome is found logarithmically.
/// Surface counters and incoming suffix-link counters identify a node exactly
/// when its last occurrence disappears, allowing an end deletion to undo only
/// local certificates. Two reversed vectors provide amortized constant-time
/// deque rebalancing while retaining indexed ch access.
template <class Char = int, class Compare = std::less<Char>>
class deque_palindromic_tree {
  struct node {
    node *fa = nullptr;
    node *lnk = nullptr;
    node *ql = nullptr;
    std::map<Char, node *, Compare> nxt;
    int len = 0;
    int cnt = 0;
    int il = 0;
  };

  struct deque_entry {
    Char ch;
    node *ps;
    node *ss;
  };

  using text_deque =
      deque_palindromic_tree_internal::amortized_deque<deque_entry>;

  text_deque s_;
  std::unique_ptr<node> odd, er;
  int dn = 0;

  node *back_appendable(const Char &ch, node *cur) {
    int n = s_.size();
    while (true) {
      if (cur->len == -1 || (cur->len < n && s_[n - cur->len - 1].ch == ch)) {
        return cur;
      }
      node *suf = cur->lnk;
      if (suf->len == -1 || s_[n - suf->len - 1].ch == ch) {
        return suf;
      }
      cur = cur->ql;
    }
  }

  node *front_appendable(const Char &ch, node *cur) {
    int n = s_.size();
    while (true) {
      if (cur->len == -1 || (cur->len < n && s_[cur->len].ch == ch)) {
        return cur;
      }
      node *suf = cur->lnk;
      if (suf->len == -1 || s_[suf->len].ch == ch) {
        return suf;
      }
      cur = cur->ql;
    }
  }

  static node *transition(node *u, const Char &ch) {
    auto it = u->nxt.find(ch);
    assert(it != u->nxt.end());
    return it->second;
  }

public:
  deque_palindromic_tree()
      : odd(std::make_unique<node>()), er(std::make_unique<node>()) {
    odd->len = -1;
    odd->fa = odd->lnk = odd->ql = odd.get();
    er->len = 0;
    er->fa = er->lnk = er->ql = odd.get();
  }

  deque_palindromic_tree(const deque_palindromic_tree &) = delete;
  deque_palindromic_tree &operator=(const deque_palindromic_tree &) = delete;
  deque_palindromic_tree(deque_palindromic_tree &&) = delete;
  deque_palindromic_tree &operator=(deque_palindromic_tree &&) = delete;

  ~deque_palindromic_tree() { clear(); }

  int size() const { return s_.size(); }
  bool empty() const { return s_.empty(); }
  int distinct_palindromes() const { return dn; }
  int longest_prefix_palindrome() const {
    return empty() ? 0 : s_.front().ps->len;
  }
  int longest_suffix_palindrome() const {
    return empty() ? 0 : s_.back().ss->len;
  }

  void push_back(const Char &ch) {
    node *fa = empty() ? odd.get() : back_appendable(ch, s_.back().ss);
    int n = size();
    node *crt = nullptr;
    node *suf = er.get();
    auto old = fa->nxt.find(ch);
    if (old == fa->nxt.end()) {
      crt = new node();
      crt->fa = fa;
      crt->len = fa->len + 2;
      if (fa != odd.get()) {
        node *sp = back_appendable(ch, fa->lnk);
        suf = transition(sp, ch);
      }
      crt->lnk = suf;
      suf->il++;
      s_.push_back({ch, er.get(), er.get()});
      n++;
      if (suf->lnk != odd.get() &&
          s_[n - suf->len - 1].ch == s_[n - suf->lnk->len - 1].ch) {
        crt->ql = suf->ql;
      } else {
        crt->ql = suf->lnk;
      }
      fa->nxt.emplace(ch, crt);
      dn++;
    } else {
      s_.push_back({ch, er.get(), er.get()});
      n++;
      crt = old->second;
      suf = crt->lnk;
    }
    s_[n - 1].ss = crt;
    s_[n - crt->len].ps = crt;
    if (suf->len >= 1 && s_[n - crt->len + suf->len - 1].ss == suf) {
      s_[n - crt->len + suf->len - 1].ss = er.get();
    }
    crt->cnt++;
  }

  void push_front(const Char &ch) {
    node *fa = empty() ? odd.get() : front_appendable(ch, s_.front().ps);
    node *crt = nullptr;
    node *suf = er.get();
    auto old = fa->nxt.find(ch);
    if (old == fa->nxt.end()) {
      crt = new node();
      crt->fa = fa;
      crt->len = fa->len + 2;
      if (fa != odd.get()) {
        node *sp = front_appendable(ch, fa->lnk);
        suf = transition(sp, ch);
      }
      crt->lnk = suf;
      suf->il++;
      s_.push_front({ch, er.get(), er.get()});
      if (suf->lnk != odd.get() && s_[suf->len].ch == s_[suf->lnk->len].ch) {
        crt->ql = suf->ql;
      } else {
        crt->ql = suf->lnk;
      }
      fa->nxt.emplace(ch, crt);
      dn++;
    } else {
      s_.push_front({ch, er.get(), er.get()});
      crt = old->second;
      suf = crt->lnk;
    }
    s_[0].ps = crt;
    s_[crt->len - 1].ss = crt;
    if (suf->len >= 1 && s_[crt->len - suf->len].ps == suf) {
      s_[crt->len - suf->len].ps = er.get();
    }
    crt->cnt++;
  }

  void pop_back() {
    assert(!empty());
    node *del = s_.back().ss;
    Char ch = s_.back().ch;
    node *suf = del->lnk;
    int n = size();
    if (del->len >= 2 && s_[n - del->len + suf->len - 1].ss->len < suf->len) {
      s_[n - del->len + suf->len - 1].ss = suf;
      s_[n - del->len].ps = suf;
    } else {
      s_[n - del->len].ps = er.get();
    }
    del->cnt--;
    if (del->il == 0 && del->cnt == 0) {
      std::size_t de1 = del->fa->nxt.erase(ch);
      assert(de1 == 1);
      suf->il--;
      delete del;
      dn--;
    }
    s_.pop_back();
  }

  void pop_front() {
    assert(!empty());
    node *del = s_.front().ps;
    Char ch = s_.front().ch;
    node *suf = del->lnk;
    if (del->len >= 2 && s_[del->len - suf->len].ps->len < suf->len) {
      s_[del->len - suf->len].ps = suf;
      s_[del->len - 1].ss = suf;
    } else {
      s_[del->len - 1].ss = er.get();
    }
    del->cnt--;
    if (del->il == 0 && del->cnt == 0) {
      std::size_t de1 = del->fa->nxt.erase(ch);
      assert(de1 == 1);
      suf->il--;
      delete del;
      dn--;
    }
    s_.pop_front();
  }

  void clear() {
    while (!empty()) {
      pop_back();
    }
  }
};

} // namespace noya