回文自动机做法讲解

是什么

简明地说,

  • 每个点表示某个前缀的最长回文后缀
  • 每条正式的有向边 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 好打、好调试,因为我们的一些初始定义解决了大部分边界问题。

猜你喜欢

转载自blog.csdn.net/weixin_43960414/article/details/112429699