前言
对于字符串 \(s\) ,\(|s|\) 表示s的长度
对于字符集 \(A\) , \(|A|\) 表示 \(A\) 的大小
本文字符串下标一律从0开始。
本文字数较多,如有错别字或者概念性错误,请联系博主或在下方回复。
SAM
后缀自动机 (suffix automaton, SAM) 是一种解决多种字符串问题的数据结构。
SAM基于一个字符串构建的,是给定字符串的所有子串的压缩形式。
标准定义为: 字符串 \(s\) 的SAM是一个接受 \(s\) 的所有后缀的最小 \(\texttt{DFA}\) (确定性有限自动机或确定性有限状态自动机)
构造SAM
我们记 \(t_0\) 为字符串的一个虚拟源点,事实上这种操作(构造虚拟节点)应用非常广泛。
那么SAM应当是:
- 有向无环图,节点为状态,边叫做转移
- 所有节点都可以由 \(t_0\) 到达
- 每个转移代表一个字母,且任意一个状态的出边的字母不同
- 存在一个或多个终止状态,使得从 $t_0 $ 到终止状态上的所有转移依访问顺序排列,对应原字符串 \(s\) 的某个后缀,且 \(s\) 的任何后缀均可以上述方式描述。
- SAM是满足以上的条件节点数最小的自动机
简单来说,没有后缀链接的SAM是一棵以 \(t_0\) 为源点的无向图。这个图的名字叫做后缀链接树。
没有后缀链接的SAM的样子
目前您暂且不需要知道后缀链接是什么。
- 空串
- 字符串 s = "a"
- 字符串 s = "abbb"
\(\tiny\texttt{借用一下oi-wiki中的图片}\)
字串的性质
SAM上的任意路径与字符串 \(s\) 字串相互映射(可以互相表示)
注意上面这点非常重要。
结束位置
结束位置,通常记作endpos,就是子串在原串中的某一个匹配的最后一个字符在原串中的下标。
我们注意到一个子串的endpos并不唯一,所以结束位置应该时一个集合。
结束位置集合,通常记作endpos集合,表示一个子串在字符串中的所有结束位置。
举个例子,字符串 "abbbabbb" 的子串 "ab" 的endpos集合为{1,5},
后缀链接
后缀链接树
SAM中所有后缀链接构成的树叫做后缀链接树。
后缀链接树非常重要,因为我们后续的针对题目的大部分操作都是在后缀链接树上进行的。
代码
原题参照洛谷[模板]后缀自动机。
map版( \(O_2\) )
时空需求都较高,但是稳定,可以处理任意字符。
对 \(O_2\) 需求很高。
#include <cstdio> #include <cstring> #include <string> #include <map> using namespace std; const int MAXN = 3000005; int sz[3000005]; struct SAM{ int size, last; struct Node{ int len, link; map<char, int> next; } nodes[MAXN]; void init(){ nodes[0].len = 0, nodes[0].link = -1; size = 1; last = 0; } void insert(char ch){ int cur = size++, p; nodes[cur].len = nodes[last].len + 1; sz[cur] = 1; for (p = last; ~p && !nodes[p].next.count(ch); p = nodes[p].link) nodes[p].next[ch] = cur; if (p == -1) nodes[cur].link = 0; else{ int q = nodes[p].next[ch]; if (nodes[p].len + 1 == nodes[q].len) nodes[cur].link = q; else{ int clone = size++; nodes[clone].len = nodes[p].len + 1; nodes[clone].next = nodes[q].next; nodes[clone].link = nodes[q].link; for ( ; ~p && nodes[p].next[ch] == q; p = nodes[p].link) nodes[p].next[ch] = clone; nodes[q].link = nodes[cur].link = clone; } } last = cur; } void build(char *buf, int len = 0){ if (!len) len = strlen(buf); for (int i = 0; i < len; ++i) insert(buf[i]); } } sam; struct Edge{ int to, next; } edges[6000005]; int head[3000005], edge_num; inline void addEdge(int u, int v){ edges[++edge_num] = (Edge){v, head[u]}; head[u] = edge_num; } inline void buildParentTree(){ for (int i = 1; i < sam.size; ++i) addEdge(sam.nodes[i].link, i); } long long ans = 0; void DFS(int u){ for (int c_e = head[u]; c_e; c_e = edges[c_e].next){ int v = edges[c_e].to; DFS(v); sz[u] += sz[v]; } if (sz[u] > 1) ans = max(ans, 1ll * sz[u] * sam.nodes[u].len); } char ch[1000005]; int main(){ sam.init(); scanf("%s", ch); sam.build(ch); buildParentTree(); string s = ch; DFS(0); printf("%lld", ans); return 0; }
数组版
时空需求较低,但是只能处理子母类问题。
#include <cstdio> #include <cstring> #include <string> using namespace std; const int MAXN = 3000005; int sz[3000005]; struct SAM{ int size, last; struct Node{ int len, link; int next[26]; } nodes[MAXN]; void init(){ nodes[1].len = 0, nodes[1].link = 0; size = 2; last = 1; } void insert(char ch){ int cur = size++, p; nodes[cur].len = nodes[last].len + 1; sz[cur] = 1; for (p = last; p && !nodes[p].next[ch - 'a']; p = nodes[p].link) nodes[p].next[ch - 'a'] = cur; if (!p) nodes[cur].link = 1; else{ int q = nodes[p].next[ch - 'a']; if (nodes[p].len + 1 == nodes[q].len) nodes[cur].link = q; else{ int clone = size++; nodes[clone].len = nodes[p].len + 1; memcpy(nodes[clone].next, nodes[q].next, sizeof(nodes[q].next)); nodes[clone].link = nodes[q].link; for ( ; p && nodes[p].next[ch - 'a'] == q; p = nodes[p].link) nodes[p].next[ch - 'a'] = clone; nodes[q].link = nodes[cur].link = clone; } } last = cur; } void build(char *buf, int len = 0){ if (!len) len = strlen(buf); for (int i = 0; i < len; ++i) insert(buf[i]); } } sam; struct Edge{ int to, next; } edges[6000005]; int head[3000005], edge_num; inline void addEdge(int u, int v){ edges[++edge_num] = (Edge){v, head[u]}; head[u] = edge_num; } inline void buildParentTree(){ for (int i = 2; i < sam.size; ++i) addEdge(sam.nodes[i].link, i); } long long ans = 0; void DFS(int u){ for (int c_e = head[u]; c_e; c_e = edges[c_e].next){ int v = edges[c_e].to; DFS(v); sz[u] += sz[v]; } if (sz[u] > 1) ans = max(ans, 1ll * sz[u] * sam.nodes[u].len); } char ch[1000005]; int main(){ sam.init(); scanf("%s", ch); sam.build(ch); buildParentTree(); string s = ch; DFS(1); printf("%lld", ans); return 0; }