From b55c9c062eb5e0ee3cd24edf8643b0b89719c5e2 Mon Sep 17 00:00:00 2001 From: "Yurii A." Date: Thu, 14 Nov 2024 14:38:42 +0200 Subject: [PATCH 1/4] Fix inconsistencies in Manacher's algorithm --- src/string/manacher.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/string/manacher.md b/src/string/manacher.md index 2c7cb35b6..b9548c989 100644 --- a/src/string/manacher.md +++ b/src/string/manacher.md @@ -49,7 +49,7 @@ Such an algorithm is slow, it can calculate the answer only in $O(n^2)$. The implementation of the trivial algorithm is: ```cpp -vector manacher_odd(string s) { +vector manacher_odd_trivial(string s) { int n = s.size(); s = "$" + s + "^"; vector p(n + 2); @@ -68,7 +68,7 @@ Terminal characters `$` and `^` were used to avoid dealing with ends of the stri We describe the algorithm to find all the sub-palindromes with odd length, i. e. to calculate $d_{odd}[]$. -For fast calculation we'll maintain the **borders $(l, r)$** of the rightmost found (sub-)palindrome (i. e. the current rightmost (sub-)palindrome is $s[l+1] s[l+2] \dots s[r-1]$). Initially we set $l = 0, r = 1$, which corresponds to the empty string. +For fast calculation we'll maintain the **exclusive borders $(l, r)$** of the rightmost found (sub-)palindrome (i. e. the current rightmost (sub-)palindrome is $s[l+1] s[l+2] \dots s[r-1]$). Initially we set $l = 0, r = 1$, which corresponds to the empty string. So, we want to calculate $d_{odd}[i]$ for the next $i$, and all the previous values in $d_{odd}[]$ have been already calculated. We do the following: @@ -145,7 +145,7 @@ vector manacher_odd(string s) { int n = s.size(); s = "$" + s + "^"; vector p(n + 2); - int l = 1, r = 1; + int l = 0, r = 1; for(int i = 1; i <= n; i++) { p[i] = max(0, min(r - i, p[l + (r - i)])); while(s[i - p[i]] == s[i + p[i]]) { From 1df43b8312aa93c6ba60488a29b7e2c29d3a9757 Mon Sep 17 00:00:00 2001 From: "Yurii A." Date: Thu, 14 Nov 2024 15:22:44 +0200 Subject: [PATCH 2/4] Add test for manacher_odd --- src/string/manacher.md | 2 +- test/test_manacher_odd.cpp | 74 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 test/test_manacher_odd.cpp diff --git a/src/string/manacher.md b/src/string/manacher.md index b9548c989..0c8bd5928 100644 --- a/src/string/manacher.md +++ b/src/string/manacher.md @@ -140,7 +140,7 @@ For calculating $d_{odd}[]$, we get the following code. Things to note: - The while loop denotes the trivial algorithm. We launch it irrespective of the value of $k$. - If the size of palindrome centered at $i$ is $x$, then $d_{odd}[i]$ stores $\frac{x+1}{2}$. -```cpp +```{.cpp file=manacher_odd} vector manacher_odd(string s) { int n = s.size(); s = "$" + s + "^"; diff --git a/test/test_manacher_odd.cpp b/test/test_manacher_odd.cpp new file mode 100644 index 000000000..72a4d6d09 --- /dev/null +++ b/test/test_manacher_odd.cpp @@ -0,0 +1,74 @@ +#include +using namespace std; + +#include "manacher_odd.h" + +string getRandomString(size_t n, uint32_t seed, char minLetter='a', char maxLetter='b') { + assert(minLetter <= maxLetter); + const size_t nLetters = static_cast(maxLetter) - static_cast(minLetter) + 1; + static std::uniform_int_distribution distr(0, nLetters - 1); + std::mt19937 gen(seed); + + string res; + res.reserve(n); + + for (size_t i = 0; i < n; ++i) + res.push_back('a' + distr(gen)); + + return res; +} + +bool testManacherOdd(const std::string &s) { + const auto n = s.size(); + const auto d_odd = manacher_odd(s); + + if (d_odd.size() != n) + return false; + + const auto inRange = [&](size_t idx) { + return idx >= 0 && idx < n; + }; + + for (size_t i = 0; i < n; ++i) { + if (d_odd[i] < 0) + return false; + for (int d = 0; d < d_odd[i]; ++d) { + const auto idx1 = i - d; + const auto idx2 = i + d; + + if (!inRange(idx1) || !inRange(idx2)) + return false; + if (s[idx1] != s[idx2]) + return false; + } + + const auto idx1 = i - d_odd[i]; + const auto idx2 = i + d_odd[i]; + if (inRange(idx1) && inRange(idx2) && s[idx1] == s[idx2]) + return false; + } + + return true; +} + +int main() { + vector testCases; + + testCases.push_back(""); + for (size_t i = 1; i <= 25; ++i) { + auto s = string{}; + s.resize(i, 'a'); + testCases.push_back(move(s)); + } + testCases.push_back("abba"); + testCases.push_back("abccbaasd"); + for (size_t n = 9; n <= 100; n += 10) + testCases.push_back(getRandomString(n, /* seed */ n, 'a', 'd')); + for (size_t n = 7; n <= 100; n += 10) + testCases.push_back(getRandomString(n, /* seed */ n)); + + for (const auto &s: testCases) + assert(testManacherOdd(s)); + + return 0; +} From 58e3437a140d809739d2f762f92ed29dd9b9b522 Mon Sep 17 00:00:00 2001 From: "Yurii A." Date: Thu, 14 Nov 2024 20:35:54 +0200 Subject: [PATCH 3/4] Update test_manacher_odd.cpp --- test/test_manacher_odd.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_manacher_odd.cpp b/test/test_manacher_odd.cpp index 72a4d6d09..cd46b7488 100644 --- a/test/test_manacher_odd.cpp +++ b/test/test_manacher_odd.cpp @@ -6,7 +6,7 @@ using namespace std; string getRandomString(size_t n, uint32_t seed, char minLetter='a', char maxLetter='b') { assert(minLetter <= maxLetter); const size_t nLetters = static_cast(maxLetter) - static_cast(minLetter) + 1; - static std::uniform_int_distribution distr(0, nLetters - 1); + std::uniform_int_distribution distr(0, nLetters - 1); std::mt19937 gen(seed); string res; From c3d9d7031a77011a50319e39dfd5d16487c4b946 Mon Sep 17 00:00:00 2001 From: Oleksandr Kulkov Date: Mon, 24 Mar 2025 20:40:28 +0100 Subject: [PATCH 4/4] empty commit to run tests?