后缀自动机的应用
求两个字符串的最长公共子串
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枚举转移,如果,则进入这个子树,否则
直到走到退出即可。

京公网安备 11010502036488号