[笔记]后缀自动机笔记
之前记过一遍了,现在快要省选了复盘一下。
后缀自动机 SAM
构建过程:
namespace Suffix_Automaton {
int h[N], e[N], ne[N], idx;
int last, sz[N], fa[N], len[N], tt;
map<int, int> ch[N];
void add(int a, int b) {
e[ ++ idx] = b, ne[idx] = h[a], h[a] = idx;
}
void ins(int w) {
int p = last, cur = ++ tt;
len[cur] = len[p] + 1; last = cur;
for (; p and (!ch[p].count(w)); p = fa[p]) ch[p][w] = cur;
if (!p) { fa[cur] = 1; }
else {
int q = ch[p][w]; if (len[q] == len[p] + 1) fa[cur] = q;
else {
int clone = ++ tt; len[clone] = len[p] + 1;
ch[clone] = ch[q]; fa[clone] = fa[q], fa[q] = fa[cur] = clone;
for (; p and ch[p][w] == q; p = fa[p]) ch[p][w] = clone;
}
} sz[cur] = 1;
}
};using namespace Suffix_Automaton;
构建过程基本是板子。只是对性质进行介绍。
-
后缀链接树
-
一个点的父亲对应的字符串,是这个点对应字符串的后缀,且是与这个点
endpos
不同的最长后缀。 -
两个前缀的最长公共后缀:后缀链接树上的 LCA。
-
-
SAM 图
- 从 \(1\) 号点出发的任意路径构成原串的一个子串。
下面是各种应用。
本质不同子串数量
由于 SAM 是 dfa,直接在上面拓扑排序求出路径数就可以了。
另外一种方法是对于 \(\mathrm{len[u]} - \mathrm{len[fa[u]]}\) 进行求和。
scanf("%d", &n); tt = last = 1;
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++ ) {
ins(a[i]); ans += len[last] - len[fa[last]];
printf("%lld\n", ans);
} return 0;
字典序 \(k\) 小子串
同样的,计算出以每个位置开头的子串数量,计算方法和上面相同,可以使用拓扑排序或者 dfs。最后从 \(1\) 号节点开始,贪心地向小的字符匹配即可。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <map>
#define rep(i, a, b) for (int i = (a); i <= (b); i ++ )
using namespace std;
const int N = 1000010;
char s[N]; int n, t, k; string S;
namespace Suffix_Automaton {
int h[N], e[N], ne[N], d[N], f[N], idx;
int last, sz[N], fa[N], len[N], tt;
int ch[N][26]; bool vis[N];
void add(int a, int b) {
e[ ++ idx] = b, ne[idx] = h[a], h[a] = idx, d[b] ++ ;
}
void ins(int w) {
int p = last, cur = ++ tt;
len[cur] = len[p] + 1; last = cur;
for (; p and (!ch[p][w]); p = fa[p]) ch[p][w] = cur;
if (!p) { fa[cur] = 1; }
else {
int q = ch[p][w]; if (len[q] == len[p] + 1) fa[cur] = q;
else {
int clone = ++ tt; len[clone] = len[p] + 1;
memcpy(ch[clone], ch[q], sizeof ch[q]); fa[clone] = fa[q], fa[q] = fa[cur] = clone;
for (; p and ch[p][w] == q; p = fa[p]) ch[p][w] = clone;
}
} sz[cur] = 1;
}
void dfs(int u) {
for (int i = h[u]; i; i = ne[i])
dfs(e[i]), sz[u] += sz[e[i]];
}
void dfs2(int u) {
if (vis[u]) return; vis[u] = 1;
rep(i, 0, 25) if (ch[u][i])
dfs2(ch[u][i]), f[u] += f[ch[u][i]];
}
void build(char *s) {
int n = strlen(s + 1); tt = last = 1;
rep(i, 1, n) ins(s[i] - 'a');
rep(i, 2, tt) add(fa[i], i); dfs(1);
rep(i, 1, tt) f[i] = t ? sz[i] : (sz[i] = 1);
sz[1] = f[1] = 0; dfs2(1);
}
};using namespace Suffix_Automaton;
bool dfs(int u, int k) {
if (k <= sz[u]) return 1;
int tmp = k - sz[u];
rep(i, 0, 25) if (ch[u][i]) {
if (tmp > f[ch[u][i]]) tmp -= f[ch[u][i]];
else { S += (char)i + 'a'; dfs(ch[u][i], tmp); return 1; }
} return 0;
}
signed main() {
scanf("%s", s + 1);
scanf("%d%d", &t, &k); build(s);
cout << (dfs(1, k) ? S : "-1") << endl;
}
字符串最小表示法
将原串复制一遍接在后面,容易发现这样包含了 \(S\) 的所有循环移位。
在这个复制后的串上做 SAM,从 \(1\) 号点开始贪心地走 \(|S|\) 步即可。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <map>
#define rep(i, a, b) for (int i = (a); i <= (b); i ++ )
using namespace std;
const int N = 1000010;
int n, a[N];
namespace Suffix_Automaton {
int h[N], e[N], ne[N], d[N], f[N], idx;
int last, sz[N], fa[N], len[N], tt;
map<int, int> ch[N]; bool vis[N];
void add(int a, int b) {
e[ ++ idx] = b, ne[idx] = h[a], h[a] = idx, d[b] ++ ;
}
void ins(int w) {
int p = last, cur = ++ tt;
len[cur] = len[p] + 1; last = cur;
for (; p and (!ch[p].count(w)); p = fa[p]) ch[p][w] = cur;
if (!p) { fa[cur] = 1; }
else {
int q = ch[p][w]; if (len[q] == len[p] + 1) fa[cur] = q;
else {
int clone = ++ tt; len[clone] = len[p] + 1;
ch[clone] = ch[q]; fa[clone] = fa[q], fa[q] = fa[cur] = clone;
for (; p and ch[p][w] == q; p = fa[p]) ch[p][w] = clone;
}
} sz[cur] = 1;
}
};using namespace Suffix_Automaton;
void dfs(int u, int l) {
if (l == n) return;
for (auto [x, y] : ch[u]) if (y) {
printf("%d ", x); dfs(y, l + 1); return;
} return;
}
signed main() {
scanf("%d", &n); rep(i, 1, n) scanf("%d", &a[i]); last = tt = 1;
rep(i, 1, n) ins(a[i]); rep(i, 1, n) ins(a[i]);
dfs(1, 0); return 0;
}