- 求一个字符串的最长回文子串最容易想到的第一个方法就是枚举每个子串,然后判断它是不是回文串,枚举子串时我们不需要创建新串,只需要比较字符串的第一个字符和最后一个字符是否相同,第二个字符和倒数第二个字符是否相同,以此类推。
- 如果一个字符串的[3, 7]这一段已经不是回文子串了,[2, 8]这一段也不可能是回文子串,这时第一种方法很多计算就白费了。我们在枚举子串的时候换一种方式来进行枚举,不是枚举它的起止位置而是尝试枚举子串的中心位置,然后再从小到大依次枚举这个子串的长度,一旦发现已经不是一个回文串了就继续尝试下一个中心位置。
- 但是一个全是a的字符串,上一种枚举方法似乎也没有多大用处。现在考虑如果以第5个字符为中心的最长回文子串的长度是5的话,这就告诉了我们[3, 7]这一段是一个回文子串。假设这时候你想要计算以第6个字符为中心的最长回文子串的长度,有什么已知的信息呢?
- 首先第6个字符和第4个字符是一样的,第7个字符和第3个字符是一样的,而第5个字符本身就肯定和第5个字符一样,那么如果[3, 5]这一段是回文子串的话,那么[5, 7]这一段肯定也是回文子串。也就是说,如果令f[i] 表示以第i个字符为中心的最长回文子串的长度,我们就会有f[i] >= f[i–2]。
- 还要考虑到f[i – 1]的值,如果f[i – 1]太小就没有意义了,应该是f(i)≥min{f(i-2), f(i-1)-2}。为什么没有意义呢?因为我们要得到f[i] >= f[i–2]必须保证i-2到i这一段是回文串,也就是说f[i-1]至少是3,所以我们需要从f[i-2]和f[i-1]-2中选一个小的。
- 我们再考虑f(5) = 1,但是f(4) = 7, f(2) = 3,理论上来说,我可以通过这些信息知道f(6)>=3,但是由于f(5)=1所以我只能计算出来f(6)>=-1。我们不应该是通过f(i – 1)来辅助计算,而是通过使得右边界(j + f(j) / 2)最大的那个j来辅助计算,所以公式将变成f(i) ≥ min{f(2*j-i) , f(j) -2*(i-j)}这种形式。2*j-i是i以j为中心点对称的那个点,f(j) -2*(i-j)是防止以j为中心回文串不包括i
- 我们只要在之前枚举中心位置那种方法的基础上,统计使得回文串右边界(j + f(j) / 2)最大的那个j,然后再计算每一个i的时候,都可以通过f(i)≥min{f(2*j-i), f(j)-2*(i-j)}这个公式来知道f(i)的一个最小值,这样即使是在我们所提到的那种最坏情况下,也可以节省掉很多不必要的计算。
- 最后考虑一个问题,我们上面考虑的解决方法只能处理长度为奇数的回文子串,那么如何解决长度为偶数的回文子串呢?我们在原来字符串的基础上,在任意两个相邻的字符间都插入一个特殊字符,这样无论是原来字符串中长度为奇数的回文子串还是长度为偶数的回文子串,在新的字符串中都有一个长度为奇数的回文子串与之进行对应。
#include <bits/stdc++.h>
using namespace std;
const int N = 1 << 23;
int rad[N];
char s[N],str[N];
int main()
{
int t;
scanf("%d",&t);
while(t --) {
scanf("%s",s);
str[0] = '$';
str[1] = '#';
int n = 2;
for(int i = 0; s[i]; i ++) {
str[n ++] = s[i];
str[n ++] = '#';
}
int ma = 0, re;
for(int i = 1; i < n; i ++) {
if(ma > i) rad[i] = min(ma - i,rad[2 * re - i]);
else rad[i] = 1;
while(str[i - rad[i]] == str[i + rad[i]]) rad[i] ++;
if(rad[i] + i > ma) {
ma = rad[i] + i;
re = i;
}
}
int ans = 0;
for(int i = 1;i < n;i ++)
ans = max(ans, rad[i] - 1);
printf("%d\n",ans);
}
}