闲话 22.12.21

闲话

什么时候结束字符串啊(
我想看多项式了(

怎么有人开始推歌了(
那我也整点杂的活吧
今日推歌是 须臾永恒 Zeno & 星尘 Minus
建议听一下 还是比较震撼的 好听!
已经循环不知道几百遍了

\减减/\减减/\减减/\减减/

广义后缀自动机

大部分是贺辰星凌的博客

简称 GSA(General Suffix Automaton)。没必要新开一篇讲,内容也不多。

SAM 帮助我们找到了一个字符串的所有子串的信息。假如我们需要多个字符串的所有子串呢?等价地,给定一棵 Trie 树后我们该如何找到所有子串呢?
我们称建立在一棵 Trie 上的后缀自动机为广义后缀自动机,简称 GSA。

我们先讲如何实现。

首先是离线构造。我们直接把串插入一棵 Trie,随后在其上 bfs。一个 Trie 的节点插入时对应的 lst 就是它在 Trie 上的父亲在 GSA 里的节点。

这个正确性应该比较显然。我们在 Trie 上 bfs 等价于插入字符串,只是仅插入一次公共前缀。这样压缩掉了 LCP,因此有最小性。

code
struct GSA {
	int mlc = 1, lens;
	int link[N], len[N], son[N][26];
	int Trie[N][26], Tmlc = 1;
	char ch[N];

	void insert() {
		cin >> ch + 1;
		lens = strlen(ch + 1);
		int p = 1;
		rep(i,1,lens) {
			if (!Trie[p][ ch[i] - 'a' ])
				Trie[p][ ch[i] - 'a' ] = ++ Tmlc;
			p = Trie[p][ ch[i] - 'a' ];
		} 
	}

	int extend(int c, int lst) {
		int now = ++ mlc, p = lst;
		len[now] = len[lst] + 1;
		while (p and !son[p][c]) 
			son[p][c] = now, p = link[p];
		if (!p) link[now] = 1;
		else {
			int q = son[p][c];
			if (len[q] == len[p] + 1) link[now] = q;
			else {
				int kage = ++ mlc;
				len[kage] = len[p] + 1, link[kage] = link[q];
				link[q] = link[now] = kage;
				memcpy(son[kage], son[q], sizeof son[kage]);
				while (p and son[p][c] == q) 
					son[p][c] = kage, p = link[p];
			}
		} return now;
	}

	int pos[N];
	queue<tuple<int,int,int> > que;
	void bfs() {
		rep (i, 0, 25) if (Trie[1][i]) 
			que.emplace(Trie[1][i], i, 1);
		while (que.size()) {
			auto[u, c, lst] = que.front(); que.pop();
			lst = extend(c, lst);
			rep (i, 0, 25) if (Trie[u][i])
				que.emplace(Trie[u][i], i, lst);
		}
	}

	void build(int m) {
		while (m --) insert();
		bfs(); 
	}
} S;

然后是在线构造。推荐写在线构造,码量小点,常数小点(\(2s\to 1s\))。

在线构造就是按照 \(SAM\) 的方式构造,每插入一个字符时最开始就把 \(lst\) 移到 \(t_0\) 位置,然后按照有改动的 \(\text{extend}\) 函数插入。函数返回当前串对应的节点作为下一次插入的 \(lst\)。那我们怎么找到当前串对应的节点呢?

我们回忆 SAM 的构造过程。假如 \(lst\) 没有沿当前字符 \(c\) 的转移,一切如常。反之我们直接跳转到“找到了这样的转移”段,按照添加新转移进行即可。如果存在 \(lst\to now\),就返回 \(now\),反之拆点。

code
struct GSA {
	int mlc = 1, lst, lens;
	int link[N], len[N], son[N][26];
	char ch[N];

	int extend(int c, int lst) {
		if (son[lst][c]) {
			int q = son[lst][c], p = lst;
			if (len[q] == len[p] + 1) return q;
			else {
				int kage = ++ mlc;
				len[kage] = len[p] + 1, link[kage] = link[q];
				link[q] = kage;
				memcpy(son[kage], son[q], sizeof son[kage]);
				while (p and son[p][c] == q) 
					son[p][c] = kage, p = link[p];
				return kage;
			} 
		} else {
			int now = ++ mlc, p = lst;
			len[now] = len[lst] + 1;
			while (p and !son[p][c]) 
				son[p][c] = now, p = link[p];
			if (!p) link[now] = 1;
			else {
				int q = son[p][c];
				if (len[q] == len[p] + 1) link[now] = q;
				else {
					int kage = ++ mlc;
					len[kage] = len[p] + 1, link[kage] = link[q];
					link[q] = link[now] = kage;
					memcpy(son[kage], son[q], sizeof son[kage]);
					while (p and son[p][c] == q) 
						son[p][c] = kage, p = link[p];
				}
			} return now;
		}
		
	}

	void build(int m) {
		while (m --) {
			cin >> ch + 1; 
			lens = strlen(ch + 1), lst = 1;
			rep(i,1,lens) lst = extend(ch[i] - 'a', lst);
		}
	}
} S;

别的不写了(

上例题吧。

GSA 维护两个字符串的信息
[HAOI2016]找相同字符 为例。

给定两个字符串,求两个字符串的相同子串数量。

相同子串想到在 SAM 上刻画,对两个字符串分别建 SAM。
然后 bfs 这两个 SAM,如果有相同的边就 bfs 过去,不能再走的位置就是一个 LCP,统计一个节点对应子串在原串上的出现次数 \(siz[A/B]\),在 bfs 时每个节点算一遍 \(siz[A]\times siz[B]\) 就是答案。

然后是 GSA 的做法。对这两个串建一个 GSA,在每个节点上统计对应子串在两个串上分别的出现次数。然后对每个节点求 \((\text{len} - \text{len}\text{ link}) \times siz[A]\times siz[B]\) 的和就是答案。



GSA + DP
[CTSC2012]熟悉的文章 为例。

题面太长,不粘。

首先二分答案转化为判定。

然后判定需要 DP。怎么 DP 呢?我们设 \(f(n)\) 为前 \(n\) 个字符最多能被覆盖多少。

首先可以不覆盖。\(f(n)\to f(n + 1)\)
然后我们需要一段 \([m, n]\) 是这些串的一个子串。这样可以转移 \(f(m) + (n - m) \to f(n)\)。假设以 \(i\) 为右端点最长的公共子串为 \(t\),需要满足 \(m \in [i - t, i - mid]\)

发现 \(i - t\) 单不减,\(f(m) - m\) 单不增。因此可以决策单调性,如果能快速求得 \(t\) 就能 \(O(n)\) 求解。

然后 \(t\) 怎么求呢?我们初始化一个指针 \(p\),初始在 \(t_0\)。每次加入一个字符就沿着 \(p\) 向这个字符对应的转移走,如果没有这样的转移就跳后缀链接。这样总时间复杂度是 \(O(n)\) 的。

因此能在 \(O(n\log n)\) 的复杂度内得到答案。

code
#include <bits/stdc++.h>
using namespace std;
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 2e6 + 10;
int n, m;

struct GSA {
	int mlc = 1, lst, lens;
	int link[N], len[N], son[N][2];
	char ch[N];

	int extend(int c, int lst) {
		if (son[lst][c]) {
			int q = son[lst][c], p = lst;
			if (len[q] == len[p] + 1) return q;
			else {
				int kage = ++ mlc;
				len[kage] = len[p] + 1, link[kage] = link[q];
				link[q] = kage;
				memcpy(son[kage], son[q], sizeof son[kage]);
				while (p and son[p][c] == q) 
					son[p][c] = kage, p = link[p];
				return kage;
			} 
		} else {
			int now = ++ mlc, p = lst;
			len[now] = len[lst] + 1;
			while (p and !son[p][c]) 
				son[p][c] = now, p = link[p];
			if (!p) link[now] = 1;
			else {
				int q = son[p][c];
				if (len[q] == len[p] + 1) link[now] = q;
				else {
					int kage = ++ mlc;
					len[kage] = len[p] + 1, link[kage] = link[q];
					link[q] = link[now] = kage;
					memcpy(son[kage], son[q], sizeof son[kage]);
					while (p and son[p][c] == q) 
						son[p][c] = kage, p = link[p];
				}
			} return now;
		}
		
	}

	void build(int m) {
		while (m --) {
			cin >> ch + 1; 
			lens = strlen(ch + 1), lst = 1;
			rep(i,1,lens) lst = extend(ch[i] - '0', lst);
		}
	}

	int cans[N];
	void init() {
		int ans = 0, p = 1, c;
		cin >> ch + 1; lens = strlen(ch + 1);
		rep(i,1,lens) {
			c = ch[i] - '0';
			if (son[p][c]) {
				++ ans, p = son[p][c];
			} else {
				while (p and !son[p][c]) p = link[p];
				if (p) ans = len[p] + 1, p = son[p][c];
				else ans = 0, p = 1;
			} cans[i] = ans;
		}
	}

	int f[N];
	int que[N], hd, tl;
	bool check(int l) {
		hd = 1, tl = 0;
		rep(i, 1, l - 1) f[i] = 0;
		rep(i, l, lens) {
			f[i] = f[i - 1];
			while (hd <= tl and f[que[tl]] - que[tl] <= f[i - l] - (i - l)) -- tl;
			que[++ tl] = i - l;
			while (hd <= tl and que[hd] < i - cans[i]) ++ hd;
			if (hd <= tl) f[i] = max(f[i], f[que[hd]] + i - que[hd]);
		} 
		return 0.9 * lens <= f[lens];
	}
} S;

signed main() {
	ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
	cin >> n >> m;
	S.build(m);
	while ( n -- ) {
		S.init();
		int l = 0, r = S.lens, mid; 
		while (l <= r) {
			mid = l + r >> 1;
			if (S.check(mid)) l = mid + 1;
			else r = mid - 1;
		} cout << l - 1 << '\n';
	}
}
posted @ 2022-12-21 22:01  joke3579  阅读(74)  评论(1编辑  收藏  举报