后缀自动机(SAM)
后缀自动机(SAM)
这里只介绍如何构造。
struct SAMNODE
{
int l, fa;
int trans[BASE];
};
一个结点记录三个东西:
- \(l\) 表示当前结点代表最长字符串的长度;
- \(fa\) 表示当前结点的父亲(即对应与当前串的最长后缀的另一个 \(\operatorname{endpos}\) 等价类);
- \(trans[x]\) 表示转移。
构造
SAM 储存 \(lst\) 与 \(cnt\),和各个结点存在 \(t\) 数组(是个结构体,就是上文的 SAMNODE)。
SAM 的构造是在线的,也就是说,每读入一个字符 \(c\),我们将当前读入的字符添加到原先的 SAM 中,逐一添加最后得到整个串的 SAM。
\(\bullet\) \(cnt\) 为当前结点个数,初始为 \(1\)。
\(\bullet\) 令 \(lst\) 表示插入上一个字符之后整个字符串对应的状态(开始时 \(lst=1\),算法最后一步更新 \(lst\))。
\(\bullet\) 创建一个新的状态 \(cur\),将 \(t[cur].l\) 赋值为 \(t[lst].l + 1\),重点就是需要维护出 \(t[cur].fa\)。
\(\bullet\) 不断跳 \(lst\) 的父亲,如果没有字符 \(c\) 的转移,就将转移指向 \(cur\),直到我们找到一个状态 \(p\) 其可以通过字符 \(c\) 转移到 \(q\),若不存在 \(p\),则一定会跳到根结点 \(1\),直接将 \(cur\) 父亲设为 \(1\) 即可,若存在 \(p\) 则我们分两种情况讨论。
\(\bullet\) 若 \(t[q].l = t[p].l + 1\),则将 \(cur\) 父亲设为 \(q\) 即可。
\(\bullet\) 若 \(t[q].l \not= t[p].l + 1\),我们此时复制状态 \(q\),创造一个新的状态 \(w\),只需更新 \(t[w].l=t[p].l + 1\),将 \(q\) 和 \(cur\) 的父亲均指向 \(w\)。
\(\bullet\) 不断跳 \(p\) 的父亲,若 \(t[p].trans[c] = q\) 则更新其为 \(w\)。
设字符集大小为 \(|\Sigma|\),可以证明渐进时间复杂度为 \(\mathcal O(n \log |\Sigma|)\),空间复杂度为 \(\mathcal O(n)\),然而如果字符集足够小,可以不写平衡树,以空间换时间将每个结点的转移存储为长度为 \(|\Sigma|\) 的数组(用于快速查询)和链表(用于快速遍历所有可用关键字)。这样算法时间复杂度为 \(\mathcal O(n)\),空间复杂度为 \(\mathcal O(n \times |\Sigma|)\)。
注意:最多会有 \(2n-1\) 个状态,所以空间需要开 \(2n\)。
#include <bits/stdc++.h>
const int N = 1e6 + 10;
const int BASE = 26;
int siz[2 * N];
int head[2 * N], to[2 * N], nxt[2 * N], tot;
inline void add(int u, int v)
{
nxt[++ tot] = head[u];
head[u] = tot;
to[tot] = v;
}
struct SAMNODE
{
int l, fa;
int trans[BASE];
};
struct SAM
{
int cnt, lst;
SAMNODE t[N * 2];
SAM () { cnt = lst = 1; }
inline void extend(int c)
{
int cur = ++ cnt, p = lst; lst = cur;
// 新建结点 cur
siz[cur] = 1;
t[cur].l = t[p].l + 1;
// 更新 t[cur].l
for (; p && ! t[p].trans[c]; p = t[p].fa)
t[p].trans[c] = cur; // 更新 c 的转移
// 找 p 满足 p 有 c 的转移
if (p)
{
int q = t[p].trans[c];
if (t[q].l == t[p].l + 1)
t[cur].fa = q;
// 若满足 t[q].l = t[p].l + 1 则直接将 t[cur].fa 设为 q
else
{
int w = ++ cnt;
t[w] = t[q]; t[w].l = t[p].l + 1;
// 复制状态 q 并更新 t[w].l
t[q].fa = t[cur].fa = w;
// 更新父亲结点
for (; p && t[p].trans[c] == q; p = t[p].fa)
t[p].trans[c] = w; // 更新转移
}
}
else t[cur].fa = 1; // 不存在 p 则将 t[cur].fa 设为根结点 1
}
} T;
char s[N]; int n; long long ans;
inline void dfs(int u)
{
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
dfs(v);
siz[u] += siz[v];
}
if (siz[u] > 1) ans = std::max(ans, 1ll * siz[u] * T.t[u].l);
}
int main()
{
scanf("%s", s + 1);
n = strlen(s + 1);
for (int i = 1; i <= n; ++ i)
T.extend(s[i] - 'a');
for (int i = 1; i <= T.cnt; ++ i)
add(T.t[i].fa, i);
dfs(1);
printf("%lld\n", ans);
return 0;
}