题意:
多个输入样例,以字符串END结束。对于每个串输出其最长回文子串
思路:
对于回文串来说,我们可能一开始会想到 (O(n^2))的 循环中心位置然后往左右两端匹配。
Manacher算法则可以将其直接降到 (O(n)) ,运用的则是回文串本身对称性的原理。
由于任意一个字符串长度有奇偶,对于奇数长度字符串我们好中心对称比较,偶数长度则必须以中间两个最为中心来匹配。
所以我们先构造一个新的串,在原来串的每个字符中间中间插入'#(也可以选择其他没有出现的任意字符)。(当然也可能有人会想,奇数串插入字符不就成了偶数串?的确,但是改变后的匹配中心仍然存在,且仍为原来串的回文中心)
我们为了防止匹配越界,我们再在首尾加入 ('$') 与' '. 为了统计子串长度我们引入一个数组(p[i]),来统计回文长度 然后就是回文匹配的递推式了(要能线性得到,那肯定是有线性递推关系的)
我们引入两个变量 (id,mx), 表示某个回文串的回文中心,以及右回文边界(边界 = 中心 + 回文半径)。而这个某个回文串则必须满足其回文边界的是在之前已经得到的回文边界中最远的那一个。
然后就是关于线性关系式的得来:
当我们记录回文串半径的数组(p[i]),遍历到 i 位置时
(1) 如果 i 位置在 mx 以内, 则根据回文的对称性, i 的回文半径 等于其 关于 id 位置对称 的 j 的回文半径。
(2) 如果 i 的位置在 mx 以外 (例如 (i_2)位置) 我们先将回文半径定位1,再用 while(str1[i-p[i]] == str1[i+p[i]]) p[i]++; 去更新
在更新完之后,如果 i 的右回文边界大于 mx , 我们则要进行更新。之后 也是一样,就线性递推过去 得到最大回文半径。
所以对于manacher算法而言,其重点就像kmp一样,求出 p[i] (回文半径数组),来辅助计算
code: 代码详细注解
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const double Pi = acos(-1.0);
const double esp = 1e-9;
const int inf = 0x3f3f3f3f;
const int maxn = 1e3+7;
const int maxm = 1e6+7;
const int mod = 1e9+7;
char str[maxm];
char str1[maxm<<1];
int len1;
int p[maxm<<1];
void init(){
int j = 0;
str1[j++]='$';
str1[j++]='#';
int len = strlen(str);
for(int i=0;i<len;i++){
str1[j++] = str[i];
str1[j++] = '#';
}
str1[j] = ' ';
len1 = j;
}
int manacher(){
init();
int res = -1;//最长回文长度
int id = 0;
int mx = 0;
for(int i=1;i<len1;i++){
//i 在最远回文距离之内
if(i < mx) p[i] = min(p[2*id-i],mx-i);
//i 在最远回文距离之外
else p[i] = 1;
//左端点为'$',右端点为' ',不需要边界判断
//更新最大半径
while(str1[i-p[i]] == str1[i+p[i]]) p[i]++;
//如果最远半径增大,更新半径边界
if(mx < (i+p[i])){
id = i;
mx = i+p[i];
}
//更新最大回文长度
res = max(res,p[i]-1);
}
return res;
}
int main(){
int cas = 0;
while(~scanf("%s",str)){
if(str[0]=='E') break;
int ans = manacher();
printf("Case %d: %d
",++cas,ans);
}
return 0;
}