回文自动机(转)
转载自此篇博客
回文自动机
类似AC自动机的一种回文串匹配自动机,也就是一棵字符树。准确的说,是两颗字符树,0号表示回文串长度为偶数的树,1号表示回文串长度为奇数的树。每一个节点都代表一个字符串,并且类似AC自动机那样,有字符基个儿子,它的第i个儿子就表示将字符基的第i个字符接到它表示的字符串两边形成的字符串。
同样类似AC自动机的是,每一个节点都有一个fail指针,fail指针指向的点表示当前串后缀中的最长回文串。特殊地,0号点的fail指针指向1,非0、1号点并且后缀中不存在回文串的节点不指向它本身,而是指向0.
变量声明:
len[i]:编号为i的节点表示的回文串的长度(一个节点表示一个回文串)。
ch[i][c]:编号为i的节点表示的回文串在两边添加字符c以后变成的回文串的编号(儿子)。
fail[i]:节点i失配以后跳转不等于自身的节点i表示的回文串的最长后缀回文串。
cnt[i]:节点i表示的本质不同的串的个数(建树时求出的不是完全的,最后重新统计一遍以后才是正确的)。
last:新添加一个字母后所形成的最长回文串表示的节点。
s[i]:第i次添加的字符(一开始设S[0] = -1,也可以是任意一个在串S中不会出现的字符)。
tot:添加的节点个数。
n:添加的字符个数。
下面是一个字符串abbaabba的回文自动机的建立过程。
这个建树的过程看起来比较抽象,实际上,回文自动机的实质就是按顺序添加字符,每添加一个字符都要找出以这个字符为后缀的最长的回文串,方法就是在以前一个为后缀的所有回文串中找到一个可以和这个点匹配的,然后再加入到树中。
注意区分一下“字符”和“节点”的概念,一个字符就是字符串中的一个字符,而回文自动机当中的节点表示了一个字符串。
我们看一道板子题
【代码实现】
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=3e5+5; 6 struct sd{ 7 int son[26],fail,len,num; 8 }t[N]; 9 int k,n,cnt=-1,id,last; 10 long long ans; 11 char st[N]; 12 void prepare() 13 { 14 t[++cnt].len=0; 15 t[++cnt].len=-1; 16 t[0].fail=1; 17 } 18 int get_fail(int v) 19 { 20 while(st[id-t[v].len-1]!=st[id]) v=t[v].fail; 21 return v; 22 } 23 void insert(char ch) 24 { 25 id++; 26 int v=ch-'a'; 27 int now=get_fail(last); 28 if(!t[now].son[v]) 29 { 30 t[now].son[v]=++cnt; 31 t[cnt].len=t[now].len+2; 32 t[cnt].fail=t[get_fail(t[now].fail)].son[v]; 33 if(t[cnt].fail==cnt) t[cnt].fail=0; 34 } 35 last=t[now].son[v]; 36 t[last].num++; 37 } 38 int main() 39 { 40 prepare(); 41 scanf("%s",st+1); 42 int k=strlen(st+1); 43 for(int i=1;i<=k;i++) 44 insert(st[i]); 45 for(int i=cnt;i>=1;i--) 46 t[t[i].fail].num+=t[i].num; 47 for(int i=1;i<=cnt;i++) 48 ans=max(ans,(long long)t[i].num*t[i].len); 49 printf("%lld",ans); 50 return 0; 51 } 52 /* 53 abacaba 54 */