A few days ago, my friend KarmaticEnding wrote a brute-force solution for Gym100543G, and surprisingly, it got accepted. Since then, we’ve been trying to either find a hack for this code or formally prove that its time complexity is lower than it looks. However, we haven’t succeeded in either direction so far.
Could you help us out?
You can find the code in Chinese annotation here: 334996600.
#include <cstdio>
#include <vector>
#include <algorithm>
#include <unordered_map>
const int MAXN = 2e5 + 7;
int T, n, rg[MAXN];
char s[MAXN];
std::unordered_map<long long, int> mp;
inline void read(int &x)
{ // Fast input: read a positive integer (no sign handling)
x = 0;
char c = getchar();
while (c < '0' || c > '9')
c = getchar();
while (c >= '0' && c <= '9')
x = (x << 3) + (x << 1) + (c & 15), c = getchar();
}
inline void read()
{ // Read a string, and insert '|' between every two characters and at both ends
s[n = 1] = '|', mp.clear();
char c = getchar();
while (c < 'A' || c > 'Z')
c = getchar();
while (c >= 'A' && c <= 'Z')
s[++n] = c, s[++n] = '|', c = getchar();
}
struct Range
{ // struct Range
int l, r;
inline bool operator<(const Range &rhs) const
{ // For sorting and deduplication
return l == rhs.l ? r + r - l > rhs.r + rhs.r - rhs.l : l < rhs.l;
}
};
inline int dac(int l, int r)
{
if (mp.count((long long)(l)*MAXN + r)) // Memoization: if the answer for this interval was already computed
return mp[(long long)(l)*MAXN + r]; // return it directly
for (int i = l; i <= r; ++i)
rg[i] = 0; // Set the radius of the longest palindrome centered at each position to 0
int mxr = 0, ans = (r - l + 1) >> 1; // Because of '|' characters, the actual number of letters in [l,r] is (r-l+1)>>1
for (int i = l, j = 0, k = 1; i <= r; i += k)
{ // Manacher template
while (i - j - 1 >= l && i + j + 1 <= r && s[i - j - 1] == s[i + j + 1])
++j;
rg[i] = j;
for (k = 1; k <= j; ++k)
{
if (rg[i] - k == rg[i - k])
break;
rg[i + k] = std::min(rg[i] - k, rg[i - k]);
}
j = std::max(j - k, 0), s[i] == '|' && (mxr = std::max(mxr, rg[i])); // Restrict to s[i]=='|' because we need even-length palindromes for recursion
}
if (mxr < 2) // If there is no palindrome of length > 1 in this interval
return mp[(long long)(l)*MAXN + r] = (r - l + 1) >> 1; // Directly return the number of characters in the interval
std::vector<Range> rgs;
std::vector<bool> nv(r - l + 1, false);
for (int i = l; i <= r; ++i)
if (s[i] == '|' && rg[i]) // i is the center of an even-length palindrome
rgs.push_back({i - rg[i], i}); // Only push the "half-interval"; the corresponding full interval is [l, r+(r-l)]
std::sort(rgs.begin(), rgs.end()); // operator< is defined based on the "full interval"
for (int i = 0, lst = -1; i < (int)(rgs.size()); ++i) // For all intervals
if ((~lst) && (rgs[i].r + rgs[i].r - rgs[i].l <= rgs[lst].r + rgs[lst].r - rgs[lst].l))
nv[i] = true; // Since they are sorted by left endpoint, if the right endpoint is contained, the interval is fully contained
else
lst = i; // This interval is not fully contained, so we keep it
for (int i = 0; i < (int)(rgs.size()); ++i)
if (!nv[i]) // All intervals with nv=true are "discarded"
ans = std::min(ans, ((r - l + 1) >> 1) - rgs[i].r + rgs[i].l + dac(rgs[i].l, rgs[i].r) + 1);
// total characters in interval - length of palindrome
// + dac(half-interval of palindrome) + 1
// i.e. remaining characters after removing palindrome + min operations for the palindrome
rgs.clear(); // Prevent memory explosion
return mp[(long long)(l)*MAXN + r] = ans;
}
int main()
{
read(T);
while (T--)
read(), printf("%d\n", dac(1, n));
return 0;
}
)
c = getchar();
while (c >= '0' && c <= '9')
x = (x << 3) + (x << 1) + (c & 15), c = getchar();
}
inline void read()
{ // Read a string, and insert '|' between every two characters and at both ends
s[n = 1] = '|', mp.clear();
char c = getchar();
while (c < 'A' || c > 'Z')
c = getchar();
while (c >= 'A' && c <= 'Z')
s[++n] = c, s[++n] = '|', c = getchar();
}
struct Range
{ // struct Range
int l, r;
inline bool operator<(const Range &rhs) const
{ // For sorting and deduplication
return l == rhs.l ? r + r - l > rhs.r + rhs.r - rhs.l : l < rhs.l;
}
};
inline int dac(int l, int r)
{
if (mp.count((long long)(l)*MAXN + r)) // Memoization: if the answer for this interval was already computed
return mp[(long long)(l)*MAXN + r]; // return it directly
for (int i = l; i <= r; ++i)
rg[i] = 0; // Set the radius of the longest palindrome centered at each position to 0
int mxr = 0, ans = (r - l + 1) >> 1; // Because of '|' characters, the actual number of letters in [l,r] is (r-l+1)>>1
for (int i = l, j = 0, k = 1; i <= r; i += k)
{ // Manacher template
while (i - j - 1 >= l && i + j + 1 <= r && s[i - j - 1] == s[i + j + 1])
++j;
rg[i] = j;
for (k = 1; k <= j; ++k)
{
if (rg[i] - k == rg[i - k])
break;
rg[i + k] = std::min(rg[i] - k, rg[i - k]);
}
j = std::max(j - k, 0), s[i] == '|' && (mxr = std::max(mxr, rg[i])); // Restrict to s[i]=='|' because we need even-length palindromes for recursion
}
if (mxr < 2) // If there is no palindrome of length > 1 in this interval
return mp[(long long)(l)*MAXN + r] = (r - l + 1) >> 1; // Directly return the number of characters in the interval
std::vector<Range> rgs;
std::vector<bool> nv(r - l + 1, false);
for (int i = l; i <= r; ++i)
if (s[i] == '|' && rg[i]) // i is the center of an even-length palindrome
rgs.push_back({i - rg[i], i}); // Only push the "half-interval"; the corresponding full interval is [l, r+(r-l)]
std::sort(rgs.begin(), rgs.end()); // operator< is defined based on the "full interval"
for (int i = 0, lst = -1; i < (int)(rgs.size()); ++i) // For all intervals
if ((~lst) && (rgs[i].r + rgs[i].r - rgs[i].l <= rgs[lst].r + rgs[lst].r - rgs[lst].l))
nv[i] = true; // Since they are sorted by left endpoint, if the right endpoint is contained, the interval is fully contained
else
lst = i; // This interval is not fully contained, so we keep it
for (int i = 0; i < (int)(rgs.size()); ++i)
if (!nv[i]) // All intervals with nv=true are "discarded"
ans = std::min(ans, ((r - l + 1) >> 1) - rgs[i].r + rgs[i].l + dac(rgs[i].l, rgs[i].r) + 1);
// total characters in interval - length of palindrome
// + dac(half-interval of palindrome) + 1
// i.e. remaining characters after removing palindrome + min operations for the palindrome
rgs.clear(); // Prevent memory explosion
return mp[(long long)(l)*MAXN + r] = ans;
}
int main()
{
read(T);
while (T--)
read(), printf("%d\n", dac(1, n));
return 0;
}