KMP算法
回文串问题
查找一个字符串中的最长回文子串
暴力算法
对于每个字符串,枚举每个位置并向两边扩展,看是否回文。
缺点:1.无法处理偶数长度串 2.平均时间复杂度(O(n^2))
manacher算法
- manacher对字符串的预处理
对于偶数长度字符串的预处理,使得它变成奇数长度字符串,即在字符串的首位加上一个特定字符,如aa
变成#a#a#
,这使得原本长度为(n)的字符串变为(2n+1),保证是奇数。
这里解释一下为什么预处理后不会影响对字符串的扩展匹配。比如我们的原字符串是a
,假设预处理后的字符串是#a#a#
,我们在任意一个点,比如字符#
,向两端匹配只会出现a
匹配a
,#
匹配#
的情况,不会出现原字符串字符与特殊字符匹配的情况,这样就能保证我们不会改变原字符串的匹配规则。通过这个例子,你也可以发现实际得到的结果与上述符合。
补充:首尾可以放置不同的字符,如^
或&
,这样可以自动判断跳出循环,而不用担心越界
2. 说明
(1)对于一个回文串,有且仅有一个对称中心,叫做回文对称中心。
(2)在一个回文串内,任选一段区间(X),一定存在关于“回文对称中心”对称的一个区间,把这个区间叫做关于区间(X)的对称区间。区间和对称区间是全等的。
(3)如果一个区间的对称区间是回文串,那么这个区间也一定是一个回文串。在大的回文串内,它们的回文串半径相等。
(4)我们通过确定关系预先得到的回文半径,它的数值必定小于等于这个位置真实的回文串半径。
因此,我们如果可以记录以每个位置为中心的回文串半径,当我们通过另一个回文中心将这个原先的中心点对称过去的时候,就可以确定对称过去的那个点的回文半径了。
考虑另一个回文中心如何确定,就是那个极大回文串的回文中心,也就是边界顶着右边我们已知的最远位置的,最长的回文串。
然而考虑到,我们只能确认我们已知的回文串内的对称关系和回文半径等量关系,关于这个极大回文串右侧,我们啥也不知道。
记录这些数据到p数组,同时记录一个mid,一个r,分别代表已经确定的右侧最靠右的回文串的对称中心和右边界。
那么我们扫描到一个新的字符时候,怎么先确定它的部分回文半径呢?若当前扫描到的位置为i,(midle ile r),我们就可以找到他的一个对称点,位置是(2 imes mid-i)
所以,拓展一个新点时,我们不必从这个点左右两边第一个位置开始向两边拓展,可以预先确定一部分回文串。所以可以看出字符串的线性复杂度。
- 若扩展一个新的关于该字符的回文半径,可以先确定一部分p[i]。
- 我们知道我们能确定的范围,其右侧不得大于r,即(p_i+i-1le r),移项得(p_ile r-i+1)
取一个min,所以p[i]=min(p[mid*2-i],r-i+1);
,最终答案是max(p[i])-1
code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=11000002;
char data[maxn<<1];
int p[maxn<<1],cnt,ans;
inline void qr(){
char c=getchar();
data[0]='~',data[cnt=1]='|';
while(c<'a'||c>'z') c=getchar();
while(c>='a'&&c<='z') data[++cnt]=c,data[++cnt]='|',c=getchar();
}
int main(){
qr();//优化后的读入
for(int t=1,r=0,mid=0;t<=cnt;++t){
if(t<=r) p[t]=min(p[(mid<<1)-t],r-t+1);
while(data[t-p[t]]==data[t+p[t]]) ++p[t];
//暴力拓展左右两侧,当t-p[t]==0时,由于data[0]是'~',自动停止。故不会下标溢出。
//假若我这里成功暴力扩展了,就意味着到时候r会单调递增,所以manacher是线性的。
if(p[t]+t>r) r=p[t]+t-1,mid=t;
//更新mid和r,保持r是最右的才能保证我们提前确定的部分回文半径尽量多。
if(p[t]>ans) ans=p[t];
}
printf("%d
",ans-1);
return 0;
}