废话
绅士曾言:哈希只比马拉车多了只 \(\log\) 而已,不用在意。
然后他写的马拉车 + 线段树 T 了,正解是使用前缀后缀记录最小最大值。
事实证明一只 \(\log\) 的复杂度优化也是必要的,所以。。。
不可能学什么后缀数组 + 快速 LCA 的。
Manacher
如何求一个字符串的最长回文子串?
首先,因为子串长度有奇有偶不便处理,所以不妨在原字符串两相邻字符之间以及两端都加上一个特殊字符。
这样我们就只用处理长度为奇数的串了。
令 \(p_{i}\) 表示以第 \(i\) 个字符为中心的最长回文子串的「半径」,假设我们已经处理好了 \(p_{1 \sim i - 1}\),如何快速求出 \(p_{i}\)?
我们知道 \([i - p_{i} + 1, i + p_{i} - 1]\) 这一段区间一定是对称的,那么可否利用「对称」这个性质来快速推导呢?
我们记录一个「最靠右」的对称区间(假设为 \([l, r]\),「最靠右」是指 \(r\) 最大),若 \(i\) 在这个区间之外,那么暴力求出 \(p_{i}\)。
若 \(i\) 在区间之内,那么说明 \(p_{i} \geqslant \min(p_{l + r - i}, r - i + 1)\),因为在这个对称区间中以 \(l + r - i\) 为中心和以 \(i\) 为中心的半径为 \(\min(p_{l + r - i}, r - i + 1)\) 的子串一定相同,并且 \(\min(p_{l + r - i}, r - i + 1) \leqslant p_{l + r - i}\) 所以这个相同子串也一定是回文串。
但是这个子串可能会因为 \(\min\) 的限制「卡」在了边界 \(r\) 处,这个时候暴力更新就好了。更新完后也要更新「最靠右」的对称区间。
因为 \(r\) 是不断增大的且最大为 \(2length + 1\),并且 \(l\) 和 \(r\) 是同时变化的,所以均摊下来的时间复杂度就是 \(\mathcal{O}(n)\)。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
string s, str = "V";
int n, l, r, ans, p[22000005];
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> s;
for(const auto& i : s) str.push_back(i), str.push_back('V');
n = str.length(), l = 0, r = -1;
for(int i = 0; i < n; ++i) {
if(i <= r) p[i] = min(p[l + r - i], r - i + 1);
while(i - p[i] >= 0 && i + p[i] < n && str[i - p[i]] == str[i + p[i]]) ++p[i];
if(i + p[i] - 1 > r) l = i - p[i] + 1, r = i + p[i] - 1;
ans = max(ans, p[i]);
}
cout << ans - 1;
return 0;
}
标签:子串,min,Manacher,str,区间,对称
From: https://www.cnblogs.com/A-box-of-yogurt/p/18023665