Manacher

P3805 【模板】Manacher

manacher 主要是用来解决与回文串有关问题的算法。

其核心思想是维护当前求出来的右端点最大的回文串。我们设 \(d_i\) 表示位置 \(i\) 的最大回文半径。我们先只考虑奇回文串
对于我们维护的这个回文串,对于位置 \(i\),其关于回文串的中点的对称点为 \(j\),那么容易注意到如果 \(i,j\) 都被包裹在这个回文串中,那么有 \(d_i\ge \min(r-i+1,d_j)\)

这个是比较显然的,因为在当前维护的这个回文串内,所有对称的位置都是完全相同的。

如果 \(i\) 不在这个回文串中,那么由于单独一个点也是回文串,因此已经求出来的右端点最大的回文串的右端点至少也是 \(i\) 了。

那么无论怎样,直接暴力向右拓展右端点即可。由于右端点不降,暴力全局总复杂度 \(O(n)\),因此可以做到总体线性。

manacher 算法的扩展性实际上非常强,有很多地方都会用到类似的思想,也就是可以 \(O(1)\) 找到与当前位置转移相同的点的位置。比如 Z 函数的求解过程就与 manacher 非常相似。

code

一个 trick 是回文串分为偶回文串和奇回文串,分开统计比较麻烦。因此考虑在每个字符之间插入一个相同的特殊字符 #。这样可以注意到我们将偶回文强制变成了奇回文。同时容易发现回文串长度就是 \(d_i-1\)

另一个是可能拓展到数组最左边外面导致越界,因此在最开始插入一个唯一的特殊字符即可。

点击查看代码
#include<bits/stdc++.h>
bool Mbe;
using namespace std;
#define ll long long
//namespace FIO{
//	template<typename P>
//	inline void read(P &x){P res=0,f=1;char ch=getchar();while(ch<'0' || ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0' && ch<='9'){res=(res<<3)+(res<<1)+(ch^48);ch=getchar();}x=res*f;}
//	template<typename Ty,typename ...Args>
//	inline void read(Ty &x,Args &...args) {read(x);read(args...);}
//	inline void write(ll x) {if(x<0ll)putchar('-'),x=-x;static int sta[35];int top = 0;do {sta[top++] = x % 10ll, x /= 10ll;} while (x);while (top) putchar(sta[--top] + 48);}
//}
//using FIO::read;using FIO::write;
const int N=1.1e7+7;
int d[N<<1];
char s[N<<1];
bool Med;
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    string S;cin>>S;int len=S.length();S=' '+S;
    int cnt=0;s[++cnt]='*';s[++cnt]='#';
    for(int i=1;i<=len;i++)s[++cnt]=S[i],s[++cnt]='#';
    len=cnt;
    int mid=0,r=0,ans=0;
    for(int i=2;i<len;i++){
        int j=2*mid-i,k=i>r?1:min(r-i+1,d[j]);
        while(s[i-k]==s[i+k])k++;
        if(i+k-1>r)r=i+k-1,mid=i;
        d[i]=k;ans=max(ans,d[i]);
    }
    #undef mid
    cout<<ans-1;
    cerr<<'\n'<<1e3*clock()/CLOCKS_PER_SEC<<"ms\n";
    cerr<<'\n'<<fabs(&Med-&Mbe)/1048576.0<<"MB\n";
    return 0;
}

P4555 [国家集训队] 最长双回文串

算是简单应用。

注意到我们只需要在每个 # 处找到以其开始的最长回文串和以其结束的最长回文串即可。
于是有一个 DP 的思路,将所有极长回文串的信息挂在两端,然后直接一个顺序扫一个逆序扫统计即可。

code

点击查看代码
#include<bits/stdc++.h>
bool Mbe;
using namespace std;
#define ll long long
//namespace FIO{
//	template<typename P>
//	inline void read(P &x){P res=0,f=1;char ch=getchar();while(ch<'0' || ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0' && ch<='9'){res=(res<<3)+(res<<1)+(ch^48);ch=getchar();}x=res*f;}
//	template<typename Ty,typename ...Args>
//	inline void read(Ty &x,Args &...args) {read(x);read(args...);}
//	inline void write(ll x) {if(x<0ll)putchar('-'),x=-x;static int sta[35];int top = 0;do {sta[top++] = x % 10ll, x /= 10ll;} while (x);while (top) putchar(sta[--top] + 48);}
//}
//using FIO::read;using FIO::write;
const int N=3e5+7;
char s[N],S[N];
int d[N],pre[N],suf[N];
bool Med;
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    cin>>(S+1);int len=strlen(S+1),cnt=0;
    s[++cnt]='^',s[++cnt]='#';
    for(int i=1;i<=len;i++){s[++cnt]=S[i],s[++cnt]='#';}
    len=cnt;
    for(int i=2,r=0,mid=0;i<=len;i++){
        int j=2*mid-i,k=r<i?1:min(r-i+1,d[j]);
        while(s[i-k]==s[i+k]){k++;}
        if(i+k-1>r){r=i+k-1,mid=i;}
        d[i]=k;pre[i+k-1]=max(pre[i+k-1],d[i]-1);suf[i-k+1]=max(suf[i-k+1],d[i]-1);
    }
    for(int i=2;i<=len;i+=2){suf[i]=max(suf[i],suf[i-2]-2);}
    for(int i=len-2;i>=2;i-=2){pre[i]=max(pre[i],pre[i+2]-2);}  //这里的 pre 和 suf 指代的是原串在 # 处的最长回文串。
    int ans=0;
    for(int i=2;i<=len;i+=2){if(pre[i]&&suf[i])ans=max(ans,pre[i]+suf[i]);}
    cout<<ans<<'\n';
    cerr<<'\n'<<1e3*clock()/CLOCKS_PER_SEC<<"ms\n";
    cerr<<'\n'<<fabs(&Med-&Mbe)/1048576.0<<"MB\n";
    return 0;
}
posted @ 2025-10-29 21:40  all_for_god  阅读(14)  评论(0)    收藏  举报