回文树学习小记

回文树(回文自动机)学习小记(Palindromic Tree)

回文树,顾名思义,用回文串构成的一棵树,结合了一点AC自动机的思想,打起来不长,用起来挺方便。

变量

   首先满足AC自动机的结构。
   定义集合tree:
       fail (指向该节点表示的回文串最长回文后缀在树中的节点,如abbba就是ababbabbba的最长回文后缀)。
       son[26] (表示在当前节点表示的回文串两边都加上一个'a'...'z'构成的回文串在树中的节点是什么)。
       len (表示当前节点表示的回文串的长度)。
       sum (表示整个串中节点表示的回文串出现多少次
     tree[i].fail,tree[i].son[j],tree[i].len,tree[i],sum
     定义last。当前加入第i个字符,last为1...i-1的串的最长回文后缀所在树中的节点。
     定义tot为现在回文树中最后一个节点是什么。

建树

两个初始节点0和1,0(分为根节点为0和1的两棵树),0的树都是长度是偶数的字符串,1的树都是长度为奇数的字符串 ,0节点一开始的长度len为0,在两边加入同样的字符后长度就为2了,所以之后都为偶数;同理,1节点一开始的长度为-1,在两边加入同样的字符就是长度+2,之后长度就为1了,之后都为奇数,这个len设为-1不仅是为了上面的说法,更是为了方便计算,也可以想象为是开始就少了一个字符。0的fail是1,因为如果有字符串的最长回文后缀是0,那么这个长度就为1,自己无法指向自己,所以所以就指向0,当时0代表的是偶数,所以还要向奇数的根节点连一条变(本来两者可以互不相连),让它转移到奇数那去,也可以想为0旁边并没有少字符,这样做免去了特判。1不用连,他的初始值为零,但因为len=-1,省去了特判,后面再讲。
每次加入一个字符,判断s[i]是否等于s[i-tree[last].len-1],否则last=tree[last].fail。因为现在要求的是加进一个字符后的最长回文后缀,所以要判断上一个最长回文后缀的前一个是否等于s[i],否则沿着最长回文后缀链走,知道找到一个字符串符合s[i]As[i]为止,A可以为空串(偶数),少一个字符(-1奇数),这也是为什么0要重新连向1de原因,因为连向0的必定是长度为1的奇数串。但是当last跳到1节点时(从0才能到1,最长回文子串就是自己),是否特判。不用!因为tree[1].len=-1,s[i-(-1)-1]=s[i],刚好处理出这个问题。为什么有最长回文后缀链可以走(新的A也应该是1..i-1的字符串的后缀,为什么一次会处理出多个后缀?),实际上因为已经处理出了回文串,所以前面的回文串可以对称过来,对称之后末尾刚好也是i-1的加入有这样的,那就有最长回文后缀链走(有点抽象),如图(a):s是整个串,p是1..i-1的字符串,t是它的最长回文后缀,x是s[i],xBx是xAx的最长回文后缀。为什么会有那么多后缀是以i-1结尾的,但是都已经处理过了。下一层的字符串的确是上一层的最长回文后缀,但是没有在i-1的点就全处理完,由于t是回文串,t下面的完全可以对称过去,在前面处理完,可能加了x的还没处理。现在加了一个x之后,在回文树中加入新的节点,更新len,son,fail。如果要更新fail,就继续沿着最长回文后缀链走就可以了,因为想xBx是xAx的后缀,对称过去,一定处理过。

回文树学习小记_第1张图片

应用

manachar行的回文树几乎都行
1、求串S前缀0~i内本质不同回文串的个数(两个串长度不同或者长度相同且至少有一个字符不同便是本质不同)
2、求串S内每一个本质不同回文串出现的次数
3、求串S内回文串的个数(其实就是1和2结合起来)
4、求以下标i结尾的回文串的个数

例题

【APIO2014】回文串(palindrome)
考虑一个只包含小写拉丁字母的符串 s。我们定义 s的一个子串 t的“出现值”为 t在 s中的出现次数乘以t的长度。 请你求出s的所有 回文子串中的最大出现值。

Code

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
#define ll long long
using namespace std;
const int maxn=300005;
int i,k,l,t,n,m,tot,last,tasl;
char s[maxn];
struct Palindromic_Tree{
    int son[27];
    ll len;
    ll sum;
    int fail;
}tree[maxn*2];
ll ans,j;
int main(){
    freopen("palindrome.in","r",stdin);
    freopen("palindrome.out","w",stdout);
    scanf("%s",s+1);
    n=strlen(s+1);
    tree[0].fail=1;
    tree[0].len=0;
    tree[1].fail=0;
    tree[1].len=-1;
    tot=1;s[0]=-1;
    fo(i,1,n){
        while(s[i-tree[last].len-1]!=s[i])last=tree[last].fail;
        if(!tree[last].son[s[i]-'a']){
            tree[++tot].len=tree[last].len+2;
            k=tree[last].fail;
            while(s[i-tree[k].len-1]!=s[i])k=tree[k].fail;
            tree[tot].fail=tree[k].son[s[i]-'a'];
            tree[last].son[s[i]-'a']=tot;\\注意这个放下面
        }    
        last=tree[last].son[s[i]-'a'];
        tree[last].sum++;
    }
    fod(i,tot,0){
        tree[tree[i].fail].sum+=tree[i].sum;
        j=tree[i].sum*tree[i].len;
        ans=max(ans,j);
    }
    printf("%lld\n",ans);
}

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