\(1\) LG5496 【模板】回文自动机
对于 \(s\) 的每个位置,请求出以该位置结尾的回文子串个数。
\(|s|\leq 1e6\)
然后就是PAM的板子题咋感觉好像没有不是很板的PAM题呢
考虑对自动机上每个点维护一个出现次数\(cnt\),那么考虑串里面的任何一个前缀的回文后缀都是其最长回文后缀的回文后缀,所以就可以有转移
\[
cnt_{p}=cnt_{fail_p}+1
\]
然后就没有然后了。
\(2\) [APIO2014]回文串
给你一个由小写拉丁字母组成的字符串\(s\)。我们定义\(s\)的一个子串的存在值为这个子串在 \(s\)中出现的次数乘以这个子串的长度。
对于给你的这个字符串\(s\),求所有回文子串中的最大存在值。
\(|s|\leq 1e6\)
感觉还是比较妙的……或许也算是PAM的基本操作,就是求出每个回文子串的出现次数。考虑一个子串出现第\(t\)次的时候(\(t>1\)),一定是作为其他串的回文后缀出现,而显然“串的最长回文后缀唯一”的逆命题“任何串会唯一作为其他串的最长回文后缀”也是成立的。故若记录以\(u\)为\(fail\)的所有子串集合为\(\rm S(u)\),那可以直接用
\[
\rm f_u=ctn_u+\sum_{v\in S(u)}f_v
\]
其中ctn为单独出现的次数,因为可能有多个子串\(s\)都不作为其他串的最长回文后缀。
emmm一句话概括,PAM处理子串问题的时候有个特性,就是由于是递减式查询,所以一个回文串不会同时作为回文串和其他串的最长回文后缀出现。
for (int i = P.sz ; i ; i --)
P.f[P.pre[i]] += P.f[i], ans = max(ans, 1ll * P.len[i] * P.f[i]) ;
哪那么多P话,就是背啊
\(3\) LG5555 秩序魔咒
求两个串的最长公共回文子串以及该长度的出现次数。
\(\rm |S|,|T|\leq 10^6\)
恭喜发现一个新套路
观察起始\(\rm PAM\)本身是一棵树,添上了一堆奇奇怪怪的\(fail\)边才变成有向图。所以考虑,如果从奇根或者偶根同时向下dfs,走同样的转移边,那么一定可以到达同样的状态。所以考虑建两个\(\rm PAM\),一起dfs,然后算个答案即可。
void dfs(int x, int y){
if (ans == P.len[x]) res ++ ;
else if (ans < P.len[x]) res = 1, ans = P.len[x] ;
for (int i = 1 ; i <= 26 ; ++ i)
if (P.trie[x][i] && Q.trie[y][i])
dfs(P.trie[x][i], Q.trie[y][i]) ;
}
By the way,奇根/偶根都要\(dp\)一次。
\(4\) [JSOI2013]快乐的 JYY
求两个串的不同公共回文串的个数,其中不同意思是下标不同。
\(|s|,|t|\leq 10^6\)
……然而这就是前两个题结合起来。考虑先\(dp\)一遍算出来每个回文子串的出现次数,然后dfs,乘法原理计数,然后就做完了。
void dfs(int x, int y){
if (x + y > 2) ans += 1ll * P.f[x] * Q.f[y] ;
for (int i = 1 ; i <= 26 ; ++ i)
if (P.trie[x][i] && Q.trie[y][i])
dfs(P.trie[x][i], Q.trie[y][i]) ;
}
int main(){
P.Init(), Q.Init() ;
cin >> (S + 1) >> (T + 1) ;
N = strlen(S + 1), M = strlen(T + 1) ;
for (int i = 1 ; i <= N ; ++ i) P.Insert(S[i] - 'A' + 1, i, S) ;
for (int i = 1 ; i <= M ; ++ i) Q.Insert(T[i] - 'A' + 1, i, T) ;
for (int i = P.sz ; i ; -- i) P.f[P.pre[i]] += P.f[i] ;
for (int i = Q.sz ; i ; -- i) Q.f[Q.pre[i]] += Q.f[i] ;
dfs(1, 1) ; dfs(0, 0) ; cout << ans<< endl ; return 0 ;
}
\(5\) 闲扯
写模板题真是让人感到空虚……