Algorithm
Task
給定一個字符串,求其最長迴文子串
Limitations
要求時空複雜度均爲線性且與字符集大小無關。
Solution
考慮枚舉迴文串的對稱軸,將其對應的最長迴文子串長度 \(len\) 求出來,取最大值即爲答案。
首先回文串有兩種,長度爲奇數的和長度爲偶數的,第一種的對稱軸是一個字符,第二種的對稱軸在兩個字符之間。
爲了將兩種情況統一起來,我們將原字符串的每兩個相鄰字符之間和首位字符前後都加上同一個不在字符集內的其他字符,例如,將 \(aaa\) 變成 \(\#a\#a\#a\#\),這樣字符串的對稱軸一定是一個字符了。
定義迴文半徑 \(r\) 爲對稱軸到迴文串邊界的字符數量,也即對稱軸的下標,考慮新字符串的迴文半徑一定是 #
和其他字符交替出現,並以 #
結尾,因此 r
一定是奇數,而其中真正的的字符數量爲 \(\frac{r - 1}{2}\),加上另一側得字符,得到該回文串對應原字符串的迴文長度爲 \(\frac{r - 1}{2} \times 2~=~r - 1\)。
我們從左到右掃描新字符串,設當前掃描到了 \(i\),則 \(\forall j \in [1, ~i)\),\(len_j\) 已經被計算完畢。
設之前的所有迴文子串中,右端點最大的爲 \(pos\),其對應對稱軸爲 \(mid\)。
分兩種情況討論。
第一種情況,\(i < pos\),則 \(i\) 在以 \(pos\) 爲右端點,$ mid$ 爲對稱軸的大回文串中。
找到 \(i\) 關於 \(mid\) 的對稱點 \(j\) ,若 \(j\) 對應的迴文串的左端點不在大回文串的左側,由於迴文串的對稱性,對稱過去以後 \(i\) 的對應迴文串應該與 \(j\) 相同,於是有 \(len_i = len_j\)。
否則,在迴文串內部的部分一定是對稱的,對於 \(pos\) 右側的部分,則暴力向右匹配即可。
第二種情況,\(i \geq pos\),則直接進行暴力匹配。
考慮複雜度:每次暴力匹配,\(pos\) 會自增 \(1\),而單次的複雜度是 \(O(1)\) 的,因此暴力匹配的總複雜度是 \(O(|S|)\) 的,而剩下的操作都是 \(O(1)\) 的因此總的時間複雜度是線性的。
Sample
P3805 【模板】manacher算法
Description
給定一個只由小寫字母組成的迴文串 \(S\),求最長迴文子串長度。
Limitations
\(|S| \leq 1.1 \times 10^7\)
Solution
闆闆題,依然需要注意等號的位置。
在實現中,可以在字符串結尾添加另一個無關字符,這樣可以保證匹配時不會越界,並且不用手動判斷。
Code
#include <cstdio>
#include <algorithm>
const int maxn = 22000007;
int n, ans;
char S[maxn];
int len[maxn], mid[maxn];
void ReadStr();
int main() {
freopen("1.in", "r", stdin);
ReadStr();
for (int i = 1, pos = 0; i <= n; ++i) {
if (i >= pos) {
int l = pos = i;
while (S[l - 1] == S[pos + 1]) { --l; ++pos; }
len[i] = pos - i + 1;
mid[pos] = i;
} else {
int j = (mid[pos] << 1) - i;
if (len[j] < (pos - i + 1)) {
len[i] = len[j];
} else {
int l = (i << 1) - pos;
while (S[l - 1] == S[pos + 1]) { --l; ++pos; }
len[i] = pos - i + 1;
mid[pos] = i;
}
}
ans = std::max(ans, len[i]);
}
qw(ans - 1, '\n', true);
return 0;
}
void ReadStr() {
static char tmp[maxn];
int _len = 0;
do tmp[++_len] = IPT::GetChar(); while ((tmp[_len] >= 'a') && (tmp[_len] <= 'z'));
tmp[_len--] = 0;
for (int i = 1; i <= _len; ++i) {
S[++n] = '#';
S[++n] = tmp[i];
}
S[++n] = '#'; S[++n] = '$';
}