首页 > 编程语言 >Manacher 算法

Manacher 算法

时间:2024-08-19 12:05:22浏览次数:6  
标签:int Manacher sum mid 算法 ans 回文

算法介绍

\(\text{Manacher}\) 算法(又名马拉车),是一种常用于处理回文字符串的算法。其代码量很小,却可以在 \(O(n)\) 的时间复杂度内处理问题

算法思想

和其他大多数算法一样,\(\text{Manacher}\) 算法利用现有的信息获得下一部分的信息。

经典例题:给定一个字符串 \(s\)。求出其最长回文子串

\(|s|\le 1.1\times 10^7\)。

该题暴力算法法是,枚举 \(s\) 的任意一个子串 \(s_{l,r}\),判断其是否是回文串,如果是,就用其更新答案。在不加任何优化的前提下算法时间复杂度 \(O(n^3)\)。

我们思考一个回文串的性质。对于一个回文串,其必然有一个回文中心,满足这个回文串关于回文中心对称。回文中心到字符串两端的距离成为回文半径。回文中心分为两类,分别是在一个字符上:例如 \(\texttt{ababa}\) 的回文中心在中间的 \(\texttt{a}\) 上;在者是两个字符的间隙之间,例如 \(\texttt{abba}\) 的回文中心在两个 \(\texttt{b}\) 中间。为了将问题普遍化,我们在一个字符串的任意一个字符旁插入一个不存在于这个字符串的字符。例如 \(\texttt{ababa}\) 转化为 \(\texttt{\#b\#a\#b\#a\#}\),\(\texttt{abba}\) 转化为 \(\texttt{\#a\#b\#b\#a\#}\) 这样,我们把两类问题都转换为一类问题。下面讨论的均是改变后的字符串

对于任何一个回文串,回文中心都只有一个。我们枚举回文中心,定义 \(P_i\) 表示以 \(i\) 为中心的最长回文半径,定义 \(l,r,mid\) 分别为当前匹配出最靠右的回文串 \(p\) 的左右边界和回文中心。

算法的运行,我们分两种情况

  1. \(i>r\):因为 \(i\) 位于我们最靠右回文串边界之外。无法利用已知信息求解。暴力枚举其回文半径,找出 \(P_i\)。

  2. \(i\le r\):因为 \(i\) 位于一个回文串内,回文中心为 \(mid\)。我们再分两种情况讨论

  • 以 \(i\) 为回文中心的最长回文子串 \(t\) 完全包含在 \(p\)。因为回文串的性质。\(mid\) 左边必然有一个回文串关于 \(t\) 对应,记为 \(t'\)。因为回文串只有一个回文中心。所以 \(t'\) 的回文中心与 \(t\) 的回文中心关于 \(mid\) 对称。我们用 \(j\) 表示 \(t'\) 的回文中心。则 \(j=mid\times 2-i\)。也可以得到 \(P_i=P_{mid\times 2-i}\)

  • 以 \(i\) 为回文中心的最长回文子串 \(t\) 不完全包含在 \(p\)。但 \(t\) 在 \(p\) 中回文的部分一定和 \(t'\) 对应。且 \(P_j\ge j-l+1\)。根据回文的对称性,虽然无法保证 \(P_i=P_j\)。但可以保证 \(P_i\ge j-l+1=r-i+1\)。找到 \(P_i\) 的最小值后无法保证回文半径最大,继续暴力枚举

最后,在计算完 \(P_i\) 的值后,记得更新 \(l,r,mid\)。

时间复杂度分析:

虽然有很多暴力的拓展,但每次拓展后,\(r\) 都会更新,且每次更新都是往后移。移动的次数都是暴力枚举的次数。所以 \(r\) 至多被枚举到右边界,时间复杂度 \(O(n)\)

答案计算

最后还有答案计算,我们令操作中,插入了辅助字符的字符串为 \(s\)。原串为 \(s'\)。结论:则原串中最长回文串的长度为 \(\max P_i-1\)。在 \(s'\) 中的最长回文子串长度为 \(2P_i-1\)(减去中间回文中心的重叠部分),其中每个字符的前面都对应一个辅助字符(即字符 \(\texttt{\#}\) 之类),最后还多出一个辅助字符。所以原串中最长回文子串的长度为 \(\dfrac{2P_i-2}{2}=P_i-1\)

例题:

例 \(1\):【模板】manacher

模板题

点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
const int N=3e7+10;
char s[N];
int p[N],mid,r=-1,n,ans=0;
void read(){
    char ch=getchar();
    s[n]='~',s[++n]='#';//首段加上一个‘~’是为了处理越界情况
    while(ch>='a'&&ch<='z')s[++n]=ch,s[++n]='#',ch=getchar();
    s[++n]='#';
    return;
}
int main(){
    read();
    for(int i=1;i<=n;i++){
        if(i>r)p[i]=1;
        else p[i]=min(p[mid*2-i],r-i+1);
        while(s[i-p[i]]==s[i+p[i]])p[i]++;
        if(i+p[i]-1>r)r=p[i]+i-1,mid=i;
        if(p[i]>ans)ans=p[i];
    }
    printf("%d\n",ans-1);
    return 0;
}

例 \(2\):[国家集训队] 最长双回文串

对于双回文串中的每个“断点”(字符 # 的位置)。找到以其为左右端点的最长回文串 \(lef_i,righ_i\)。答案即为 \(\max\limits_{lef_i,righ_i\ne 0}lef_i+righ_i\)。由于在算法匹配中,只求出了一些点的 \(lef_i,righ_i\)。所以还需要递推来补全所有的点,然后再求解。

点击查看代码
#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
const int N=3e5+10;
char s[N];
int n,mid,r=-1,p[N],L[N],R[N],ans;
void read(){
    char ch=getchar();
    s[++n]='~';s[++n]='#';
    while(ch>='a'&&ch<='z')s[++n]=ch,s[++n]='#',ch=getchar();
    return;
}
signed main(){
    read();
    for(int i=1;i<=n;i++){
        if(i>r)p[i]=1;
        else p[i]=min(p[mid*2-i],r-i+1);
        while(s[i-p[i]]==s[i+p[i]])p[i]++;
        if(i+p[i]-1>r)r=p[i]+i-1,mid=i;
        L[i-p[i]+1]=max(L[i-p[i]+1],p[i]-1);
        R[i+p[i]-1]=max(R[i+p[i]-1],p[i]-1);
    }
    for(int i=n-2;i>=2;i-=2)R[i]=max(R[i+2]-2,R[i]);
    for(int i=4;i<=n;i+=2)L[i]=max(L[i-2]-2,L[i]);
    for(int i=2;i<=n;i+=2)if(L[i]&&R[i])ans=max(ans,L[i]+R[i]);
    printf("%lld\n",ans);
    return 0;
}

例 \(3\):[国家集训队] 拉拉队排练

\(P_i\) 并不仅仅代表以 \(i\) 为中心的最长回文半径加一。还代表原串中以 \(i\) 为回文中心的回文串的个数,因为 \([i+P_i-1,i-P_i+1],[i+P_i-2,i-P_i+2]\dots[i+1,i-1],[i,i]\) 都在最大的回文串中,回文的性质使得他们都是回文串。

如果我们在算法的过程中,用 \(sum_k\) 表示长度为 \(k\) 的回文串的差分数组。如果得到一个 \(i\) 的 \(P_i=x\),那么只需将 \(sum_x\) 加上 \(1\)。在一起计算时先计算长的回文串,在将 \(sum_x\) 加到前面去。就可以完成计算。

因为题目要求一个“小团体”长度必须为单数。所以要对一些情况特判。\(sum_i=sum_i+sum_{i+2}\)。因为 \(k\) 很大,也会用到快速幂的技巧。同时记得特判 \(-1\) 的情况。

点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
const int N=2e6+10,mod=19930726;
typedef long long ll;
ll len,k,n,mid,r=-1,p[N],maxn=0,sum[N],ans=1,z;
char s[N];
void read(){
    char ch=getchar();
    s[n]='~',s[++n]='#';
    while(ch<'a'||ch>'z')ch=getchar();
    while(ch>='a'&&ch<='z')s[++n]=ch,s[++n]='#',ch=getchar();
    return;
}
ll ksm(ll a,ll b){
    ll ans=1;
    while(b){
        if(b&1)ans=(a*ans)%mod;
        a=(a*a)%mod;
        b>>=1;
    }
    return ans;
}
int main(){
    scanf("%lld %lld",&len,&k);
    read();
    for(int i=1;i<=n;i++){
        if(i>r)p[i]=1;
        else p[i]=min(p[2*mid-i],r-i+1);
        while(s[i-p[i]]==s[i+p[i]])p[i]++;
        if(i+p[i]-1>r)r=i+p[i]-1,mid=i;
        if(s[i]!='#')sum[p[i]-1]++,maxn=max(maxn,p[i]-1);
    }
    for(int i=maxn;i>=1&&k>0;i--){
        sum[i]+=sum[i+2],z=min(k,sum[i]);
        ans=(ans*ksm(i,z))%mod;
        k-=z;
    }
    if(k>0)printf("-1\n");
    else printf("%lld\n",ans);
    return 0;
}

The End

感觉还比较有用

标签:int,Manacher,sum,mid,算法,ans,回文
From: https://www.cnblogs.com/zuoqingyuan/p/18367053

相关文章

  • ACM算法——数学专题
    一、素数1、欧拉筛时间复杂度:\(O(n)\)constexprintN=1E6;std::vector<int>primes;std::vector<bool>st;voidinit(intn){st.assign(n+1,false);primes.clear();for(inti=2;i<=n;i++){if(!st[i]){pri......
  • 经典分治算法
    RT,主要介绍一些经典的分治算法CDQ分治经典人类智慧算法。三维偏序问题三维偏序是CDQ分治的一个经典应用,搭配树状数组可以在\(O(n\log^2n)\)的时间复杂度内解决问题。如果我们枚举每一个元素,然后枚举其他的元素的话,可以在\(O(n^2)\)的时间复杂度解决这个问题,但显然无法......
  • 粒子群算法和引力搜索算法的混合算法(PSOGSA)优化BP神经网络原理及matlab代码
    目录0引言1数学模型2模型对比3matlab代码3.1伪代码示意图3.2PSOGSA-BP4视频讲解0引言基于已发表智能算法文献研究,SeyedaliMirjalili等人在发现PSO的开发能力与GSA的探索能力有者较好结合性能,因此基于二者算法优势点提出混合算法PSOGSA。该算法主要利用PSO鸟......
  • 粒子群算法和引力搜索算法的混合算法(PSOGSA)优化长短期记忆神经网络原理及matlab代码
    目录0引言1数学模型2模型对比3matlab代码3.1伪代码示意图3.2PSOGSA-LSTM4视频讲解0引言基于已发表智能算法文献研究,SeyedaliMirjalili等人在发现PSO的开发能力与GSA的探索能力有者较好结合性能,因此基于二者算法优势点提出混合算法PSOGSA。该算法主要利用PSO......
  • 粒子群算法和引力搜索算法的混合算法(PSOGSA)优化支持向量机原理及matlab代码
    目录0引言1数学模型2模型对比3matlab代码3.1伪代码示意图3.2PSOGSA-SVM4视频讲解0引言基于已发表智能算法文献研究,SeyedaliMirjalili等人在发现PSO的开发能力与GSA的探索能力有者较好结合性能,因此基于二者算法优势点提出混合算法PSOGSA。该算法主要利用PSO......
  • 莫队算法
    莫队是一种优美的暴力,分为普通莫队、树上莫队、带修莫队、回滚莫队等,但是这个人太菜了导致只会普通莫队。首先,%%%莫涛大神。传奇人物就直接放图吧:stostostostostostostostostostostostostostostoMoTaoorzorzorzorzorzorzorzorzorzorzorzorzorzo......
  • 合成数的高效算法
    数的合成指将多个数字合成一个整数,比如将9、5、2、7合成9527。本文主要讨论的是整数的合成,附带提一下字符型数的合成。一、整数的合成整数合成指的是输入的数字和输出的整数都是以数的形式(即数据类型全部为int型)存储,而不用字符串。有两种算法:1.位值法(看似简单实则复杂低......
  • 叠Buff!经典麻雀优化算法+多重双向深度学习!SSA-BiTCN-BiGRU-Attention多输入单输出回
    叠Buff!经典麻雀优化算法+多重双向深度学习!SSA-BiTCN-BiGRU-Attention多输入单输出回归预测目录叠Buff!经典麻雀优化算法+多重双向深度学习!SSA-BiTCN-BiGRU-Attention多输入单输出回归预测效果一览基本介绍程序设计参考资料效果一览基本介绍1.Matlab实现SS......
  • 【智能算法】回溯算法
    目录一、回溯算法概述二、回溯算法分类2.1组合问题2.2排列问题2.3切割问题2.4子集和问题2.5图论问题2.6其他问题三、回溯算法C语言实现3.1组合问题3.2排列问题3.3切割问题3.4子集和问题3.5图论问题四、回溯算法应用一、回溯算法概述      ......
  • 机器学习:线性回归算法(一元和多元回归代码)
    1、线性回归         1、数据准备:描述如何获取和准备数据。    2、图像预处理:包括图像读取。    3、将数据划分为训练集和测试集。    4、计算数据的相关系数矩阵。    5、模型训练:详细说明如何使用线性回归算法训练模型,包括......