后缀自动机学习小记
一、 的性质
是个状态机。一个起点,若干终点。原串的所有子串和从 起点开始的所有路径一一对应,不重不漏。所以终点就是包含后缀的点。- 每个点包含若干子串,每个子串都一一对应一条从起点到该点的路径。且这些子串一定是里面最长子串的连续后缀。
问题中经常考虑两种边:- 普通边,类似于
。表示在某个状态所表示的所有子串的后面添加一个字符。 、 。表示将某个状态所表示的最短子串的首字母删除。这类边构成一棵树。
- 普通边,类似于
二、 的构造思路
:子串 所有出现的位置(尾字母下标)集合。 中的每个状态都一一对应一个 的等价类。 的性质:- 令
, 为 的两个子串 ,不妨设 (我们用 表示 的长度 ,此处等价于 不长于 )。则 是 的后缀当且仅当 , 不是 的后缀当且仅当 。 - 两个不同子串的
,要么有包含关系,要么没有交集。 - 两个子串的
相同,那么短串为长串的后缀。 - 对于一个状态
,以及任意的 的后缀 ,如果 的长度满足: ,那么 。
- 令
三、 的构造过程
对于字符串
这里蓝色的边是后缀自动机的普通边,而绿边则是上面说的
inline void extend(int c){
int p = last, np = last = ++ tot;
ct[np] = 1, tr[np].len = tr[p].len + 1;
这里
while(p && !tr[p].son[c]) tr[p].son[c] = np, p = tr[p].fa;
首先从
if(!p) tr[np].fa = 1;
这里判断一下,如果全都没有
if(tr[q].len == tr[p].len + 1) tr[np].fa = q;
设第一个找到的有
else{
int nq = ++ tot; tr[nq] = tr[q];
tr[nq].len = tr[p].len + 1;
tr[q].fa = tr[np].fa = nq;
while(p && tr[p].son[c] == q) tr[p].son[c] = nq, p = tr[p].fa;
}
否则新建一个点
关于求每个状态包含串的出现个数(也就是
四、 时间复杂度
线性。
证明较为复杂,略。
模板题:洛谷 P3804 【模板】后缀自动机(SAM)
题目大意:
给定一个只包含小写字母的字符串
请你求出
代码实现:
#include <bits/stdc++.h>
inline int read(){
int s = 0, f = 0; char ch = getchar();
while(!isdigit(ch)){if(ch == '-') f = 1; ch = getchar();}
while(isdigit(ch)) s = s * 10 + ch - 48, ch = getchar();
return f ? ~s + 1 : s;
}
inline int max(int x, int y){return x > y ? x : y;}
inline int min(int x, int y){return x < y ? x : y;}
const int N = 1e6 + 5;
struct node{
int len, fa;
int son[26];
}tr[N << 1];
char s[N];
int n, last = 1, tot = 1;
int ct[N << 1], ans;
int head[N << 1], ne[N << 1], to[N << 1], idx;
inline void add(int u, int v){
to[++ idx] = v, ne[idx] = head[u], head[u] = idx;
return;
}
inline void extend(int c){
int p = last, np = last = ++ tot;
ct[np] = 1, tr[np].len = tr[p].len + 1;
while(p && !tr[p].son[c]) tr[p].son[c] = np, p = tr[p].fa;
if(!p) tr[np].fa = 1;
else{
int q = tr[p].son[c];
if(tr[q].len == tr[p].len + 1) tr[np].fa = q;
else{
int nq = ++ tot; tr[nq] = tr[q];
tr[nq].len = tr[p].len + 1;
tr[q].fa = tr[np].fa = nq;
while(p && tr[p].son[c] == q) tr[p].son[c] = nq, p = tr[p].fa;
}
}
return;
}
void dfs(int u){
for(int i = head[u]; i; i = ne[i]){
dfs(to[i]);
ct[u] += ct[to[i]];
}
if(ct[u] > 1) ans = max(ans, ct[u] * tr[u].len);
return;
}
int main(){
scanf("%s", s);
n = strlen(s);
for(int i = 0; i < n; ++ i) extend(s[i] - 'a');
for(int i = 2; i <= tot; ++ i) add(tr[i].fa, i);
dfs(1);
printf("%d\n", ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律