[学习笔记]manacher
\(\operatorname{manacher}\) 算法用于处理字符串,常常用来 \(\Theta(n)\) 找回文串
第一种解法,是直接暴力计算,\(\Theta(n^3)\)
而第二种解法,是利用字符串对称的性质,把枚举端点变成枚举中点,少了一个循环,优化掉 \(\Theta(n)\) 的复杂度
我们所求的 \(\Theta(n)\) 算法,是不是能由第二种解法再利用一次字符串对称的性质得来呢?
观察下面的字符串:
其中的最长回文串为 \(\mathrm{ABAKABA}\) ,若使用第二种解法,有什么可以省去不计算的呢?
相信很容易猜到,一个回文串的左半边有一个回文串,那它的右半边也有一个,那么我们对这个回文串的计算显然可以略去
扩展到一般性质,若一个回文串里包含着另一个回文串,那这个回文串的另一边必然存在另一个与它一模一样的回文串!
然鹅这样有局限性:
\(1.\) 回文串长度的奇偶性造成了对称轴的位置可能在某字符上,也可能在两个字符之间的空隙处,要对两种情况分别处理
如何解决?考虑在字符串中插入本来不存在的字符(比如 \(\#\) )
也就是说如果原来字符串是
我们可以让它变成
顺便为了防止越界,我们钦定 \(str[0]\) 为 \(\#\)
这样我们就可以直接以每个字符为对称轴进行扩展了
\(2.\) 会出现很多子串被重复多次访问,时间效率大幅降低
我们用一个辅助数组 \(rec_i\) 表示 \(i\) 点能够扩展出的回文长度
我们先设置一个辅助变量 \(r\) ,表示已经触及到的最右边的字符
以及一个辅助变量 \(mid\) ,表示包含 \(r\) 的回文串的对称轴所在的位置
当 \(i\) 在 \(r\) 左边且在 \(mid\) 右边时:
设 \(i\) 关于 \(mid\) 的对称点为 \(j\) ,显然 \(rec_i\) 一定不会小于 \(rec_j\)
但是当 \(i+rec_i>r\) 时,我们无法保证上述情况成立,所以我们要和 \(i\) 到右边界的距离取一个 \(\min\)
显然地,\(j=mid\times 2 -i\)
那么我们可以让 \(rec_i=rec_j\) 然后从 \(i+rec_i\) 开始扩展,最后更新 \(mid\) 和 \(r\)
当 \(i\) 在 \(r\) 右边时,我们无法得知 \(rec_i\) ,只好从1开始遍历,然后更新 \(mid\) 和 \(r\)
点击查看代码
#include<cstdio>
#include<cstring>
#include<string>
#define int long long
#define WR WinterRain
using namespace std;
const int WR=20100000,INF=1099588621776,mod=1e9+7;
char ch[WR],str[WR];
int rec[WR],len;
int ans;
int read(){
int s=0,w=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<3)+(s<<1)+ch-'0';
ch=getchar();
}
return s*w;
}
void manacher(){
int r=0,mid;
for(int i=1;i<len;i++){
if(i<r){
rec[i]=min(rec[(mid<<1)-i],rec[mid]+mid-i);
}else rec[i]=1;
while(str[i+rec[i]]==str[i-rec[i]]) rec[i]++;
if(rec[i]+i>r){
r=rec[i]+i;
mid=i;
}
}
}
signed main(){
scanf("%s",ch+1);
len=strlen(ch+1);
str[0]='(',str[1]='#';
for(int i=1;i<=len;i++){
str[i<<1]=ch[i];
str[i<<1|1]='#';
}
len=((len+1)<<1);
str[len]=')';
manacher();
for(int i=1;i<len;i++) ans=max(ans,rec[i]);
printf("%lld\n",ans-1);
return 0;
}
本文来自博客园,作者:冬天丶的雨,转载请注明原文链接:https://www.cnblogs.com/WintersRain/p/16699045.html
为了一切不改变的理想,为了改变不理想的一切