回文自动机做法讲解
是什么
简明地说,
- 每个点表示某个前缀的最长回文后缀
- 每条正式的有向边 a→c→b 表示在 a 两边都加上 c 会变成 b
- 由于一条路径上的点长度奇偶性相同,所以初始两个点,0点长度为 0,1点长度为 -1
- 为了方便判断边界,规定fail[0]=1,初始孩子都为0
是不是觉得有很多疑问,没事,看代码
CODE
(众所周知,英语的多义性相比汉语要小很多)
(解释得很清晰了,老外都看得懂)
Definitions
char ss[MAXN];// Original String
struct PAM{
int s[MAXC];// Sons
int len,siz,fail;
//len: Length of the longest palindrome suffix of the prefix ;
//siz: Number of the same substrings that is one's longest palindrome suffix;
//fail: ID of the longest palindrome suffix of this suffix ;
PAM() {memset(s,0,sizeof(s));len = siz = fail = 0;}
PAM(int L,int F) {
memset(s,0,sizeof(s));len = L;siz = 0;fail = F;
}
}pam[MAXN] = {PAM(0,1),PAM(-1,1)};
int las = 0,cnt = 1,Len = 0;// ID of the latest point, Number of the points, The present length
int ct[MAXN];// The final number of times that one palindrome substring appears
definition: noun.定义
palindrome: noun.回文
Init
void rebuild() { // As its name
pam[0] = PAM(0,1); pam[1] = PAM(-1,1); // In order to be easy in the "getfail" function
las = 0;cnt = 1;Len = 0; // The same as definitions
memset(ct,0,sizeof(ct)); // This is also important
}
Important Function
int getfail(int x) {
while(ss[Len - pam[x].len - 1] != ss[Len])
x = pam[x].fail;
// The ending is point 1, so it will stop as ss[Len - (-1) - 1] obviously equals to ss[Len].
return x;
}
Adding Operation
void addpam(int c) { Len ++;
int cur = getfail(las); // First, we are supposed to find its father。
int now = pam[cur].s[c]; // (The probable point that is equal to the new one)
if(!now) { // Then if we find it distinct, we should continue.
now = ++ cnt; pam[now] = PAM(); // Build a new point ,
pam[now].len = pam[cur].len + 2; // and set the length.
pam[now].fail = pam[getfail(pam[cur].fail)].s[c]; //We should get the "fail",
pam[cur].s[c] = now; // before setting the child.
} // But what if we find that it has appeared before ? Just let it go.
pam[las = now].siz ++;// Whether it appeared or not, the last step is to change the "siz".
return ;
}
Necessary Operation
P.S. It’s used in 100% of the calculations.
void addup() {
for(int i = cnt;i > 1;i --) ct[pam[i].fail] += (ct[i] += pam[i].siz);
// Add the "siz" to itself, and its suffixs.
// We can simply do it from "cnt" to 1 since "pam[x].fail < x".
}
两个推论
- 一个长为n的字符串内本质不同的回文子串最多n个。
- 一个字符串内每个前缀的最长回文后缀都对应一个回文子串,且每种本质不同的回文子串都会出现,即形成满射。
所以回文自动机里存了一个字符串所有的回文子串。
相比起比较麻烦的 SAM+Manacher ,它可以更好地解决回文串匹配等问题。
我的意思是,它比 SAM+Manacher 好打、好调试,因为我们的一些初始定义解决了大部分边界问题。