Manacher

Manacher 学习笔记

问题

求出串 \(s\) 中最长回文串的长度。

分析

数据范围不大的情况下,估计还是可以哈希加二分解决。
但是严格线性的算法还是更加优秀的。
字符串这几个基础算法绕来绕去实际上就是一个自动机的思想,我当前想要计算某个答案,于是看看之前的答案是否能有帮助。
首先定义 \(r_i\) 为:以 \(i\) 为中心的最长回文半径是多少。
不过又遇到了问题,就是当回文串长度是偶数的时候该怎么办呢?这样的话,我们在每个空插入一个特殊字符就可以了。比如将 \(\text{abcdef}\) 变成 \(\text{a#b#c#d#e#f}\)
在递推的同时,我们记录覆盖点最右的一个回文区间,如果当前的 \(i\) 落在了这个回文区间内,说明 \(i\) 的答案就可以从 \(i\) 关于这个区间的对称点 \(2\times mid-i\) 相等地转移过来,这个时候我们再尝试去扩展 \(i\) 的回文区间,由于这个“最右覆盖点”一共最多只会移动 \(n\) 次,所以算法是严格 \(O(n)\) 的。

Code

#include<bits/stdc++.h>
using namespace std;
const int N=1.1e7+10;
char tmp[N<<1],s[N<<1];
int n,r[N<<1];
int maxpos,mid;
inline void pre()
{
    scanf("%s",tmp+1);
    n=strlen(tmp+1);
    for(int i=1;i<=2*n+1;++i)
    {
        if(i&1)s[i]='#';
        else s[i]=tmp[i>>1];
    }
    n=2*n+1;
}
inline void manacher()
{
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        if(i<=maxpos)r[i]=min(maxpos-i+1,r[2*mid-i]);
        else r[i]=1;
        while(1<=i-r[i]&&i+r[i]<=n&&s[i-r[i]]==s[i+r[i]])r[i]++;
        if(i+r[i]-1>maxpos)maxpos=i+r[i]-1,mid=i;
        if(s[i+r[i]-1]=='#')ans=max(ans,r[i]-1);
        else ans=max(ans,r[i]);
    }
    cout<<ans;
}
int main()
{
    pre();
    manacher();
    return 0;
}
posted @ 2025-04-12 17:57  Hanggoash  阅读(15)  评论(0)    收藏  举报
动态线条
动态线条end