[笔记]后缀自动机笔记

之前记过一遍了,现在快要省选了复盘一下。

后缀自动机 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\) 号点出发的任意路径构成原串的一个子串。

下面是各种应用。

本质不同子串数量

P4070 [SDOI2016] 生成魔咒

由于 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\) 小子串

P3975 [TJOI2015] 弦论

同样的,计算出以每个位置开头的子串数量,计算方法和上面相同,可以使用拓扑排序或者 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;
}
posted @ 2024-11-28 21:10  Link-Cut-Y  阅读(1)  评论(0编辑  收藏  举报