Manacher

废话

绅士曾言:哈希只比马拉车多了只 \(\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;
}
posted @ 2024-02-20 17:36  A_box_of_yogurt  阅读(12)  评论(2编辑  收藏  举报
Document