回文自动机

作用

好像是2015年时战斗民族某巨佬发明的,可以快速求一个串里有多少本质不同的回文子串以及每个回文子串出现的次数。

实现

回文自动机由两棵树构成,一棵是 even 树,另一棵是 odd 树。每个节点对应了一个回文子串,有如下信息:

  • len :该回文子串的长度。
  • fail :该回文子串最长回文后缀对应的节点。
  • son[k] :在该回文字串两端添加 k 字符之后到达的节点。

很像AC自动机啊……特别的, even 根节点(下文记为 even )以及 odd 根节点(下文记为 odd )的 fail 都是 odd ,且 leneven=0 lenodd=1

回文自动机和后缀自动机一样,每次是从上一状态拓展过来的,所以需要记录当前节点 p (刚开始 p=even )。假设现在在构造字符串 s ,这次添加了字符 si ,那么就要找到 p fail 树上的最近祖先 p (即 p p 的回文后缀),让 p 对应回文字串左边的字符为 si (即 si=silenp1 ,读者不妨将 even odd 代入,体会两个根节点的作用),这样就找到了包含 si 的最长回文字串 sonp[si]

找到 sonp[si] 之后我们需要构造 sonp[si] fail (如果 sonp[si] 原先不存在的话),其实很简单,只需要再找一次 failp fail 树上的最近祖先 k ,让 si=silenk1 即可。

效率

因为回文子串最多 |s| 个,所以 p “下降”或“上升”最多 |s| 次,时间复杂度为 O(|s|) ,空间复杂度为 O(|s|) (其中 表示字符集大小),如果用map就时空复杂度就是 O(|s|log2) O(|s|) 了。

模板

BZOJ3676,另一种方法是后缀数组+Manacher……你们自己体会一下。
这里写图片描述

#include
#include
#include
using namespace std;
typedef long long LL;
const int maxn=300000,maxi=26;

int n,num[maxn+5];char s[maxn+5];LL ans;
int si,p,son[maxn+5][maxi],fai[maxn+5],len[maxn+5];

inline void Extend(char *s,int i){
    while (s[i]!=s[i-len[p]-1]) p=fai[p];
    if (!son[p][s[i]-'a']){
        si++;int k=fai[p];len[si]=len[p]+2;
        while (s[i]!=s[i-len[k]-1]) k=fai[k];
        fai[si]=son[k][s[i]-'a'];son[p][s[i]-'a']=si;
    }
    p=son[p][s[i]-'a'];num[p]++;
}
int main(){
    freopen("PAM.in","r",stdin);
    freopen("PAM.out","w",stdout);
    scanf("%s",s+1);n=strlen(s+1);
    si=1;p=0;fai[0]=fai[1]=1;len[0]=0;len[1]=-1;
    for (int i=1;i<=n;i++) Extend(s,i);
    for (int i=si;i>1;i--) ans=max(ans,(LL)num[i]*len[i]),num[fai[i]]+=num[i];
    return printf("%lld\n",ans),0;
}

进阶

前端插入以及不基于势能分析的插入算法

你可能感兴趣的:(回文自动机)