This documentation is automatically generated by online-judge-tools/verification-helper
#define PROBLEM "https://yukicoder.me/problems/no/599"
#include "./../../Library/String/LCPArray.hpp"
#include <iostream>
#include "./../../Library/DataStructure/SegmentTree.hpp"
using ll = long long;
using std::cin;
using std::cout;
constexpr char endl = '\n';
struct Preprocessing {
Preprocessing() {
std::cin.tie(0);
std::ios::sync_with_stdio(0);
};
} _Preprocessing;
struct F {
auto operator()(int a, int b) const { return std::min(a, b); };
};
using M = mtd::Monoid<int, static_cast<int>(1e9), F>;
constexpr ll MOD = 1000000007;
signed main() {
std::string s;
cin >> s;
ll size = s.size();
auto lcparray = mtd::LCPArray(s);
auto segtree = mtd::SegmentTree<M>(size, lcparray.getLCPArray());
auto sai = lcparray.getSuffixArrayIndexList();
ll half = (size >> 1);
std::vector<ll> dp(half + 1);
dp[0] = 1;
for (int l = 0; l < half; ++l) {
for (int r = l; r < half; ++r) {
auto idx1 = sai[l];
auto idx2 = sai[size - r - 1];
if (idx2 < idx1) { std::swap(idx1, idx2); }
auto lcp = segtree.query(idx1, idx2 - 1);
int len = r - l + 1;
if (lcp >= len) {
dp[r + 1] += dp[l];
if (dp[r + 1] >= MOD) { dp[r + 1] -= MOD; }
}
}
}
ll ans = 0;
for (const auto& x : dp) {
ans += x;
if (ans >= MOD) { ans -= MOD; }
}
cout << ans;
}#line 1 "Test/String/LCPArray.test.cpp"
#define PROBLEM "https://yukicoder.me/problems/no/599"
#line 2 "Library/String/LCPArray.hpp"
#line 2 "Library/String/SuffixArray.hpp"
#include <iostream>
#include <list>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#line 2 "Library/Algorithms/BinarySearch.hpp"
#include <concepts>
#include <numeric>
#include <ranges>
#include <type_traits>
namespace mtd {
template <class Lambda>
auto binarySearch(double ok, double ng, int rep, const Lambda& is_ok) {
for ([[maybe_unused]] auto _ : std::views::iota(0, rep)) {
double mid = (ok + ng) / 2.0;
(is_ok(mid) ? ok : ng) = mid;
}
return ok;
}
template <class Lambda, std::integral T1, std::integral T2>
auto binarySearch(T1 ok_, T2 ng_, const Lambda& is_ok) {
using T = std::common_type_t<T1, T2>;
T ok = ok_, ng = ng_;
while (std::abs(ok - ng) > 1) {
T mid = (ok + ng) >> 1;
(is_ok(mid) ? ok : ng) = mid;
}
return ok;
}
} // namespace mtd
#line 10 "Library/String/SuffixArray.hpp"
namespace mtd {
/**
* SuffixArrayを構築する
* O(N)
* 文字列の全てのsuffixをソートした配列が得られる
* ex) abadc -> [0, 2, 1, 4, 3]([abadc, adc, badc, c, dc])
*
* SA-IS(Suffix Array - Induced Sort)で実装
*/
class SuffixArray {
enum class TYPE { L, S, LMS };
const std::string m_str;
const std::vector<int> m_suffixArray;
/* string to vector<int> */
static std::vector<int> toIntVec(const std::string& str) {
std::vector<int> vec;
vec.reserve(str.size() + 1);
for (const auto& c : str) { vec.emplace_back(c - '0' + 1); }
vec.emplace_back(0);
return vec;
}
/* classify { L, S, LMS } */
static std::vector<TYPE> classifying(const std::vector<int>& str) {
auto sz = static_cast<int>(str.size());
auto typeArray = std::vector<TYPE>(sz);
typeArray[sz - 1] = TYPE::S;
for (int i = sz - 2; i >= 0; --i) {
if (str[i] == str[i + 1]) {
typeArray[i] = typeArray[i + 1];
continue;
}
typeArray[i] = (str[i] < str[i + 1]) ? TYPE::S : TYPE::L;
}
for (int i = 1; i < sz; ++i) {
if (typeArray[i - 1] == TYPE::L && typeArray[i] == TYPE::S) {
typeArray[i] = TYPE::LMS;
}
}
return typeArray;
}
/* induced sort */
static std::vector<int> inducedSort(const std::vector<int>& str,
const std::vector<TYPE>& type,
std::list<int>& lmsList) {
auto sz = str.size();
auto nList = std::set<int>();
for (const auto& c : str) { nList.emplace(c); }
auto befCheck = [&](int k, auto& addList, bool rev) {
if (k == 0) { return; }
if (!rev && type[k - 1] == TYPE::L) {
addList[str[k - 1]].emplace_back(k - 1);
}
if (rev && type[k - 1] != TYPE::L) {
addList[str[k - 1]].emplace_front(k - 1);
}
};
auto checkAndUpdate = [&](int n, auto& checkList, auto& addList,
bool rev) {
auto& lst = checkList[n];
if (rev) {
for (auto itr = lst.rbegin(); itr != lst.rend(); ++itr) {
befCheck(*itr, addList, rev);
}
} else {
for (const auto& k : lst) { befCheck(k, addList, rev); }
}
};
/* set LMS */
auto tailList = std::unordered_map<int, std::list<int>>();
for (const auto& i : lmsList) { tailList[str[i]].emplace_back(i); }
/* set L-type */
auto headList = std::unordered_map<int, std::list<int>>();
for (const auto& n : nList) {
checkAndUpdate(n, headList, headList, false);
checkAndUpdate(n, tailList, headList, false);
}
/* set S-type*/
tailList = std::unordered_map<int, std::list<int>>();
for (auto itr_n = nList.rbegin(); itr_n != nList.rend(); ++itr_n) {
auto n = *itr_n;
checkAndUpdate(n, tailList, tailList, true);
checkAndUpdate(n, headList, tailList, true);
}
/* merge */
auto suffixArray = std::vector<int>();
suffixArray.reserve(sz);
for (const auto& n : nList) {
for (const auto& c : headList[n]) { suffixArray.emplace_back(c); }
for (const auto& c : tailList[n]) { suffixArray.emplace_back(c); }
}
return suffixArray;
}
/* order lms -> sorted lms */
static std::unordered_map<int, int> getLmsChanger(
const std::vector<int>& str, const std::vector<TYPE>& type,
const std::list<int>& lms) {
if (lms.size() == 1) {
return std::unordered_map<int, int>{{str.size() - 1, 0}};
}
std::unordered_map<int, int> changer{
{static_cast<int>(str.size()) - 1, 0}, {*++lms.begin(), 1}};
int num = 1;
for (auto itr = ++lms.begin(); itr != (--lms.end());) {
auto f1 = *itr;
auto f2 = *(++itr);
bool isSame = false;
for (int i = 0;; ++i) {
if (str[f1 + i] != str[f2 + i]) { break; }
auto b1 = (type[f1 + i] == TYPE::LMS);
auto b2 = (type[f2 + i] == TYPE::LMS);
if ((b1 || b2) && i > 0) {
if (b1 && b2) {
isSame = true;
break;
}
break;
}
}
if (!isSame) { ++num; }
changer.emplace(f2, num);
}
return changer;
}
/* calc Suffix Array*/
static std::vector<int> constructSuffixArray(const std::vector<int>& str) {
auto type = classifying(str);
/* calc fake Suffix Array using order seed*/
auto lmsOrder = [&type]() {
auto lms = std::list<int>();
for (size_t i = 0; i < type.size(); ++i)
if (type[i] == TYPE::LMS) { lms.emplace_back(i); }
return lms;
}();
auto fakeArray = inducedSort(str, type, lmsOrder);
/* calc true seed */
auto lmsFakeOrder = [&fakeArray, &type]() {
auto lms = std::list<int>();
lms.emplace_back(static_cast<int>(type.size()) - 1);
for (const auto& i : fakeArray)
if (type[i] == TYPE::LMS) { lms.emplace_back(i); }
return lms;
}();
auto changer = getLmsChanger(str, type, lmsFakeOrder);
auto& lmsTrueOrder = lmsFakeOrder;
if (changer[*lmsFakeOrder.rbegin()] + 1 <
static_cast<int>(lmsFakeOrder.size())) {
/* exist same lms-substring */
auto s = std::vector<int>();
auto def = std::vector<int>();
s.reserve(lmsOrder.size());
def.reserve(lmsOrder.size());
for (const auto& c : lmsOrder) {
s.emplace_back(changer[c]);
def.emplace_back(c);
}
auto lmsSuffixArray = constructSuffixArray(s);
lmsTrueOrder = std::list<int>{static_cast<int>(type.size()) - 1};
for (const auto& c : lmsSuffixArray) {
lmsTrueOrder.emplace_back(def[c]);
}
}
/* calc true Suffix Array using true seed */
auto suffixArray = inducedSort(str, type, lmsTrueOrder);
return suffixArray;
}
public:
SuffixArray(const std::string& str)
: m_str(str), m_suffixArray(constructSuffixArray(toIntVec(str))) {}
/**
* 引数として与えられたpattern出現位置の区間を返す
*/
std::pair<int, int> findPattern(const std::string& pattern) const {
auto find = [&](const std::string& ptn) {
int end = static_cast<int>(m_suffixArray.size());
int ptn_sz = static_cast<int>(ptn.size());
auto ret = binarySearch(end, -1, [&](int mid) {
int st = m_suffixArray[mid];
int sub_sz = end - st;
for (int k = 0; k < std::min(ptn_sz, sub_sz); ++k) {
if (ptn[k] < m_str[st + k]) { return true; }
if (ptn[k] > m_str[st + k]) { return false; }
}
return ptn_sz <= sub_sz;
});
return ret;
};
auto patternUpper = [&pattern]() {
auto ptn = pattern;
++*ptn.rbegin();
return ptn;
}();
auto fl = find(pattern);
auto fu = find(patternUpper);
return {fl, fu};
}
auto getSuffixArray() const { return m_suffixArray; }
/* output fot debug */
void debugOutput() const {
for (const auto& x : m_suffixArray) { std::cout << x << " "; }
std::cout << std::endl;
auto end = m_str.size();
for (const auto& x : m_suffixArray) {
std::cout << m_str.substr(x, end) << std::endl;
}
}
};
} // namespace mtd
#line 4 "Library/String/LCPArray.hpp"
namespace mtd {
/**
* LCPArrayを構築する
* O(N)
* suffix arrayで隣接するstrの最長共通接頭辞(LCP:Longest Common Prefix)を得る
* ex) sa:[aab, ab, abaab, b, baab] -> LCPA:[1, 2, 0, 1]
*
* Kasai's algorithmで実装
*/
class LCPArray {
const std::vector<int> m_suffixArray;
const std::vector<int> m_lcpArray;
static std::vector<int> constructLcpArray(const std::string& str) {
auto sz = static_cast<int>(str.size());
const auto suffixArray = SuffixArray(str).getSuffixArray();
auto rank = std::vector<int>(sz);
for (int i = 0; i < sz; ++i) { rank[suffixArray[i]] = i; }
auto lcpArray = std::vector<int>(sz - 1);
for (int i = 0, h = 0; i < sz; ++i) {
if (rank[i] == 0) { continue; }
int j = suffixArray[rank[i] - 1];
if (h > 0) { --h; }
for (; j + h < sz && i + h < sz; ++h) {
if (str[i + h] != str[j + h]) { break; }
}
lcpArray[rank[i] - 1] = h;
}
return lcpArray;
}
public:
LCPArray(const std::string& str)
: m_suffixArray(SuffixArray(str).getSuffixArray()),
m_lcpArray(constructLcpArray(str)) {}
auto getLCPArray() const { return m_lcpArray; }
auto getSuffixArrayIndexList() const {
std::vector<int> sail(m_suffixArray.size());
for (unsigned int i = 0; i < m_suffixArray.size(); ++i) {
sail[m_suffixArray[i]] = i;
}
return sail;
}
};
} // namespace mtd
#line 4 "Test/String/LCPArray.test.cpp"
#line 6 "Test/String/LCPArray.test.cpp"
#line 2 "Library/DataStructure/SegmentTree.hpp"
#include <deque>
#line 5 "Library/DataStructure/SegmentTree.hpp"
#include <utility>
#line 7 "Library/DataStructure/SegmentTree.hpp"
#line 2 "Library/Algebraic/Monoid.hpp"
#line 4 "Library/Algebraic/Monoid.hpp"
namespace mtd {
template <class S, // set
S element, // identity element
class op // binary operation
>
requires std::is_invocable_r_v<S, op, S, S>
struct Monoid {
using value_type = S;
constexpr static S _element = element;
using op_type = op;
S m_val;
constexpr Monoid(S val) : m_val(val) {}
constexpr Monoid() : Monoid(element) {}
constexpr Monoid binaryOperation(const Monoid& m2) const {
return op()(m_val, m2.m_val);
}
friend std::ostream& operator<<(std::ostream& os,
const Monoid<S, element, op>& m) {
return os << m.m_val;
}
};
namespace __detail {
template <typename T, template <typename, auto, typename> typename S>
concept is_monoid_specialization_of = requires {
typename std::enable_if_t<std::is_same_v<
T, S<typename T::value_type, T::_element, typename T::op_type>>>;
};
} // namespace __detail
template <typename M>
concept monoid = __detail::is_monoid_specialization_of<M, Monoid>;
} // namespace mtd
#line 9 "Library/DataStructure/SegmentTree.hpp"
namespace mtd {
template <monoid Monoid>
class SegmentTree {
private:
const int m_size;
std::vector<Monoid> m_node;
using S = decltype(Monoid().m_val);
constexpr int calcSize(int n) const {
int size = 1;
while (size < n) { size <<= 1; }
return size;
}
template <class Lambda>
constexpr auto _update_op(int itr, Monoid&& val, const Lambda& op) {
int i = itr + m_size - 1;
m_node[i] = op(m_node[i], std::forward<decltype(val)>(val));
while (i) {
i = (i - 1) >> 1;
m_node[i] = m_node[(i << 1) | 1].binaryOperation(m_node[(i + 1) << 1]);
}
}
constexpr auto _query(int _l, int _r) const {
auto l = std::max(_l, 0) + m_size;
auto r = std::min(_r, m_size - 1) + m_size;
auto lm = Monoid();
auto rm = Monoid();
while (l <= r) {
if (l & 1) {
lm = lm.binaryOperation(m_node[l - 1]);
++l;
}
if (!(r & 1)) {
rm = m_node[r - 1].binaryOperation(rm);
--r;
}
l >>= 1, r >>= 1;
}
return lm.binaryOperation(rm);
}
constexpr auto _construct(const std::vector<S>& vec) {
for (unsigned int i = 0; i < vec.size(); ++i) {
m_node[i + m_size - 1] = Monoid(vec[i]);
}
for (int i = m_size - 2; i >= 0; --i) {
m_node[i] = m_node[(i << 1) | 1].binaryOperation(m_node[(i + 1) << 1]);
}
}
public:
SegmentTree(int n) : m_size(calcSize(n)), m_node((m_size << 1) - 1) {}
SegmentTree(int n, const std::vector<S>& vec) : SegmentTree(n) {
_construct(vec);
}
template <class Lambda>
constexpr auto update_op(int itr, Monoid&& val, const Lambda& op) {
return _update_op(itr, std::forward<Monoid>(val), op);
}
constexpr auto update(int itr, Monoid&& val) {
return update_op(itr, std::forward<Monoid>(val),
[](const Monoid&, const Monoid& m2) { return m2; });
}
constexpr auto add(int itr, Monoid&& val) {
return update_op(itr, std::forward<Monoid>(val),
[](const Monoid& m1, const Monoid& m2) {
return Monoid(m1.m_val + m2.m_val);
});
}
constexpr auto query(int l, int r) const { return _query(l, r).m_val; }
constexpr auto query_all() const { return m_node[0].m_val; }
/*
* f([l, r]) = true となる最大のr
* judge: (Monoid) -> bool
**/
template <class F>
constexpr auto max_right(int _l, const F& judge) const {
if (!judge(Monoid())) {
throw std::runtime_error("SegmentTree.max_right.judge(e) must be true");
}
auto l = std::max(_l, 0) + m_size;
auto r = 2 * m_size - 1;
auto lm = Monoid();
while (l <= r) {
if (l & 1) {
auto next = lm.binaryOperation(m_node[l - 1]);
if (!judge(next)) {
auto itr = l;
while (itr < m_size) {
auto litr = 2 * itr;
auto ritr = 2 * itr + 1;
auto lval = lm.binaryOperation(m_node[litr - 1]);
if (!judge(lval)) {
itr = litr;
} else {
itr = ritr;
std::swap(lm, lval);
}
}
return itr - m_size - 1;
}
std::swap(lm, next);
++l;
}
l >>= 1, r >>= 1;
}
return m_size - 1;
}
/*
* f([l, r]) = true となる最小のl
* judge: (Monoid) -> bool
**/
template <class F>
constexpr auto min_left(int _r, const F& judge) const {
if (!judge(Monoid())) {
throw std::runtime_error("SegmentTree.min_left.judge(e) must be true");
}
auto l = m_size;
auto r = std::min(_r, m_size - 1) + m_size;
auto rm = Monoid();
while (l <= r) {
if (l & 1) { ++l; }
if (!(r & 1) || (_r == m_size - 1 && r == 1)) {
auto next = m_node[r - 1].binaryOperation(rm);
if (!judge(next)) {
auto itr = r;
while (itr < m_size) {
auto litr = 2 * itr;
auto ritr = 2 * itr + 1;
auto rval = m_node[ritr - 1].binaryOperation(rm);
if (!judge(rval)) {
itr = ritr;
} else {
itr = litr;
std::swap(rm, rval);
}
}
return itr - m_size + 1;
}
std::swap(rm, next);
--r;
}
l >>= 1, r >>= 1;
}
return 0;
}
constexpr auto debug() const {
for (int i = 0; i < m_size; ++i) {
std::cout << m_node[m_size + i - 1] << " ";
}
std::cout << std::endl;
}
};
} // namespace mtd
#line 8 "Test/String/LCPArray.test.cpp"
using ll = long long;
using std::cin;
using std::cout;
constexpr char endl = '\n';
struct Preprocessing {
Preprocessing() {
std::cin.tie(0);
std::ios::sync_with_stdio(0);
};
} _Preprocessing;
struct F {
auto operator()(int a, int b) const { return std::min(a, b); };
};
using M = mtd::Monoid<int, static_cast<int>(1e9), F>;
constexpr ll MOD = 1000000007;
signed main() {
std::string s;
cin >> s;
ll size = s.size();
auto lcparray = mtd::LCPArray(s);
auto segtree = mtd::SegmentTree<M>(size, lcparray.getLCPArray());
auto sai = lcparray.getSuffixArrayIndexList();
ll half = (size >> 1);
std::vector<ll> dp(half + 1);
dp[0] = 1;
for (int l = 0; l < half; ++l) {
for (int r = l; r < half; ++r) {
auto idx1 = sai[l];
auto idx2 = sai[size - r - 1];
if (idx2 < idx1) { std::swap(idx1, idx2); }
auto lcp = segtree.query(idx1, idx2 - 1);
int len = r - l + 1;
if (lcp >= len) {
dp[r + 1] += dp[l];
if (dp[r + 1] >= MOD) { dp[r + 1] -= MOD; }
}
}
}
ll ans = 0;
for (const auto& x : dp) {
ans += x;
if (ans >= MOD) { ans -= MOD; }
}
cout << ans;
}