回文自动机

作用

好像是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<cstdio>
#include<cstring>
#include<algorithm>
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;
}

进阶

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值