后缀自动机的应用
求两个字符串的最长公共子串
SPOJ1811 Longest Common Substring
方法:
对于字符串建
,考虑
串从头到尾枚举。假设枚举到第
位。
用表示当前(到第
位)在
走到的节点,
表示当前匹配的以字符
(
串的第
个字符)结尾的子串中最大的长度。
若,则
直到
为
。
否则,,
此时满足
到根节点
的路径上的所有点,都是合法的子串。显然最大的就是
。跟答案取大就行了。
int ans = 0, n = strlen(s + 1), now = 0, p = 1; for(int i = 1; i <= n; ++i) { int c = s[i] - 'a'; if(ch[p][c]) p = ch[p][c], ++now; else { while(p && !ch[p][c]) p = fa[p]; if(!p) now = 0; else now = len[p] + 1, p = ch[p][c]; } ans = max(ans, now); }
求多个串的最长公共子串
SPOJ1812 Longest Common Substring II
方法:
对第一个串建。然后对于剩下的串,都从头到尾遍历一遍。按照前一题的思路,
假设枚举到第位。
用表示当前(到第
位)在
走到的节点,
表示当前匹配的以字符
(
串的第
个字符)结尾的子串中最大的长度。
若,则
直到
为
。
否则,,
此时满足
到根节点
的路径上的所有点,都是合法的子串。
然后每个节点取子树里的最大值,然后取自己和这个最大值的最小值。
对于每个节点与答案取最小值。
memset(f, 0, sizeof f); for(int i = 1; i <= n; ++i) { int c = s[i] - 'a'; if(ch[p][c]) p = ch[p][c], ++now; else { while(p && !ch[p][c]) p = fa[p]; if(!p) now = 0; else now = len[p] + 1, p = ch[p][c]; } f[p] = max(f[p], now); } for(int i = cnt; i >= 1; --i) { int q = fa[a[i]]; f[q] = max(f[q], f[a[i]]); f[a[i]] = min(f[a[i]], len[a[i]]); g[a[i]] = min(g[a[i]], f[a[i]]); } int ans = 0; for(int i = 1; i <= cnt; ++i) ans = max(ans, g[i]);
求循环同构在主串里的出现次数
方法:
对主串建
。
按照上面两题的思路,表示匹配到的
上的节点。
表示在当前匹配的字符串末尾加上字符
表示删除当前匹配的字符串的首字符
计算答案。
具体细节直接看代码:
void go(int c) { while(x && !ch[x][c]) l = len[x = fa[x]]; if(ch[x][c]) { x = ch[x][c]; ++l; } } void cal() { if(l == n && vis[x] < TT) { ans += siz[x]; vis[x] = TT; } } void del() { if(l > n && (--l == len[fa[x]])) x = fa[x]; } void solve() { ans = 0; scanf("%s", s +1); n = strlen(s +1); x = 1; l = 0; for(int i = 1; i <= n; ++i) go(s[i] - 'a'); cal(); for(int i = 1; i < n; ++i) { go(s[i] - 'a'); del(); cal(); } printf("%d\n", ans); }
本质不同的子串个数
方法:
每加一个字符,显然只会增加个不同子串,直接算答案。
求排名第
小的子串
方法:
按照树遍历的类似思路,假设当前遍历到节点
,从
a
到z
枚举转移,如果,则进入这个子树,否则
直到走到退出即可。