[学习笔记]manacher

\(\operatorname{manacher}\) 算法用于处理字符串,常常用来 \(\Theta(n)\) 找回文串
第一种解法,是直接暴力计算,\(\Theta(n^3)\)
而第二种解法,是利用字符串对称的性质,把枚举端点变成枚举中点,少了一个循环,优化掉 \(\Theta(n)\) 的复杂度
我们所求的 \(\Theta(n)\) 算法,是不是能由第二种解法再利用一次字符串对称的性质得来呢?
观察下面的字符串:

\[\mathrm{O A K A B A K A B A O A K} \]

其中的最长回文串为 \(\mathrm{ABAKABA}\) ,若使用第二种解法,有什么可以省去不计算的呢?
相信很容易猜到,一个回文串的左半边有一个回文串,那它的右半边也有一个,那么我们对这个回文串的计算显然可以略去
扩展到一般性质,若一个回文串里包含着另一个回文串,那这个回文串的另一边必然存在另一个与它一模一样的回文串!
然鹅这样有局限性:

\(1.\) 回文串长度的奇偶性造成了对称轴的位置可能在某字符上,也可能在两个字符之间的空隙处,要对两种情况分别处理

如何解决?考虑在字符串中插入本来不存在的字符(比如 \(\#\)
也就是说如果原来字符串是

\[\mathrm{ABAKKAB} \]

我们可以让它变成

\[\mathrm{\#A\#B\#A\#K\#K\#A\#B\#} \]

顺便为了防止越界,我们钦定 \(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;
}
posted @ 2022-09-16 11:34  冬天丶的雨  阅读(24)  评论(0编辑  收藏  举报
Live2D