回文自动机学习笔记

回文自动机是一个跟后缀自动机很像的东西qaq

首先关于一个串的回文子串有几个性质
1:一个串 S S S至多只有 ∣ S ∣ |S| S个不同的回文子串
2:每次向一个串尾部添加字符,至多产生一个新的回文子串,如果产生新的回文子串,其一定是包含串尾的回文子串中最长的一个
证明了(2)其实就证明了(1)
我们来证一下(2):
假设我们向串尾添加了一个字符,产生了新的回文子串,那么其一定含串尾,
假设这个新的包含串尾的最长回文子串是这样一部分
这里写图片描述
(蓝色是新添加的字符,红色是新产生的含串尾最长的回文子串,绿色是对称轴)
如果还产生了新的别的回文子串,那他也一定包含串尾,且长度比这个红串小,假设他是棕色的串
这里写图片描述
容易发现,不管棕色的串是否越过对称轴,根据红串回文的性质,在原串中都一定已经存在一个紫色的串和他相同,那他就不是一个新的回文串

证明了这个我们接下来介绍回文自动机

回文自动机(又叫回文树)是一个维护了原串所有回文子串的自动机,他能够识别原串的所有回文子串,统计每种子串的出现次数,也资瓷统计每种的出现位置(我感觉用线段树合并是资瓷的,虽然好像没见过和出现位置相关的题=v=)

他维护的是两棵树(其实我一直觉得这好像不是一个树的结构qaq,可能这样说方便理解?),一棵代表偶数长度的回文串,一棵代表奇数长度
因为和SAM很像,所以要记录当前自动机内的点数,添加完上个字符后保留串尾原串的最长回文子串节点last
然后每个点有trans指针(表示向这个回文串两边添加字符得到的回文串),fail指针(表示从该串前端删去尽量少的字符能得到的回文串,这个和SAM的parent有点像),cnt(在原串出现次数),len(该回文串长度),(出现位置一般不维护)

然后直接讲建自动机吧
0号节点代表偶数串的树根,1号节点代表奇数串的树根
一开始初始化所有trans都是0,fail[0]=1,len[0]=0,len[1]=-1
为啥len[1]=-1这个看下面的构建过程就会发现这是非常自然且优美的

假设我们已经构建好了串S的回文自动机,我们现在要在串尾添加一个字符a
因为一个回文串(len>2)一定是由一个小一点回文串在两边添加字符得到的,我们从last开始,不断尝试S[i-1-len[p]]和S[i]是否相同,若相同,串p就可以向两端加上a变成回文串np,可以发现对于len<=2的回文串,因为len[0]=0,len[1]=-1,我们这样判也能得到要找的回文子串,且因为每次跳fail跳到的都是在前端缩尽可能少的字符,所以得到的就是包含新串Sa串尾a的最长的回文子串(这个地方和SAM挺像的=v=)
如果p原来没有trans[p][a]这个孩子,那么就产生了新的回文串np,对于fail[np],我们可以从t=fail[p]开始按上述跳p的步骤跳,跳到一个合法的t后,trans[t][a]就是fail[np](这里又和AC自动机很像)
如果有就np=trans[p][a]
然后last=np,cnt[last]++

然后就建完了qaq
哦对了关于cnt,建树时只会对每个前缀的最长回文后缀cnt++,求真实出现次数的话,要建完后按节点编号从大到小,cnt[fail[p]]+=cnt[p]
回文自动机的题一般都比较裸?有些套路也和SAM很像

最大的好处是代码短呀OvO

粘个板子(BZOJ3676)

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
 
const int maxn = 310000;
const int maxc = 30;
 
int n;
char str[maxn]; int S[maxn];
 
struct Tree
{
    int son[maxn][maxc],fail[maxn],len[maxn],cnt[maxn];
    int tot,last;
    void init()
    {
        tot=1,last=0;
        memset(son[0],0,sizeof son[0]);
        memset(son[1],0,sizeof son[1]);
        fail[0]=1,fail[1]=0;
        len[0]=0,len[1]=-1;
    }
    int newnode(int l)
    {
        tot++;
        memset(son[tot],0,sizeof son[tot]);
        len[tot]=l,fail[tot]=0;
        return tot;
    }
    void extend(const int i)
    {
        int p=last,w=S[i],np;
        while(S[i-1-len[p]]!=w) p=fail[p];
        if(!son[p][w])
        {
            np=newnode(len[p]+2);
            int t=fail[p];
            while(S[i-1-len[t]]!=w) t=fail[t];
            fail[np]=son[t][w];
            son[p][w]=np;
        }
        else np=son[p][w];
        cnt[last=np]++;
    }
}Tr;
 
int main()
{
    //freopen("tmp.in","r",stdin);
    //freopen("tmp.out","w",stdout);
     
    scanf("%s",str); n=strlen(str);
    S[0]=-1; for(int i=0;i<n;i++) S[i+1]=str[i]-'a';
     
    Tr.init(); for(int i=1;i<=n;i++) Tr.extend(i);
    for(int i=Tr.tot;i>=2;i--) Tr.cnt[Tr.fail[i]]+=Tr.cnt[i];
     
    ll ans=0;
    for(int i=2;i<=Tr.tot;i++) ans=max(ans,(ll)Tr.cnt[i]*Tr.len[i]);
    printf("%lld\n",ans);
     
    return 0;
}

一些模板题
bzoj2565
回文自动机来回扫预处理一些,枚举XY的断点,用manacher好像也能做

bzoj2342
发现一个串如果是双倍回文,有
他的长度是4的倍数
他自身是回文串
他的一半也是回文串
建出回文自动机,建出fail树,求出每个回文串长度的一半是否是回文串
用manacher好像也行qaq

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值