后缀自动机(SAM) 学习笔记

后缀自动机(SAM) 学习笔记

很久以前学过SAM,现在又忘了。

学习资料

后缀自动机感性理解

史上最通俗的后缀自动机详解

后缀自动机 (SAM)

SAM

如果我们把一个长度为 n n n 的串 S S S 的所有后缀放入同一个 trie 中,并标记结束位置end,可以得到一个时间、空间均为 O ( n 2 ) \mathcal O(n^2) O(n2) 的(假)后缀树。

它具有以下几个性质:

  1. 从根到任意 end 是一个后缀,从根到任意节点是一个子串。
  2. 本质不同的子串个数就是状态(节点)个数。

但是它的时空太大,不满足我们的需求,所以我们采用它的强压缩版本——SAM。

字符串 的 SAM 是一个接受 字符串的所有后缀的最小 DFA (确定性有限自动机或确定性有限状态自动机)。它是一个DAG。

SAM的构造

结束位置 endpos

对于一个 S S S 的子串 t t t,我们用 e n d p o s ( t ) \mathrm{endpos}(t) endpos(t) 表示 t t t S S S 中的所有 结束位置。

如果 两个子串 t 1 , t 2 t_1,t_2 t1,t2 满足 e n d p o s ( t 1 ) = e n d p o s ( t 2 ) \mathrm{endpos}(t_1)=\mathrm{endpos}(t_2) endpos(t1)=endpos(t2),则称它们为 等价类

除初始状态外,每一个等价类对应SAM上的一个状态(节点)。当然可以把初始状态看成空串。

endpos 有以下性质:

  1. 如果 t 1 , t 2 ( ∣ t 1 ∣ ≤ ∣ t 2 ∣ ) t_1,t_2(|t_1|\le|t_2|) t1,t2(t1t2) 的 endpos 相同,那么 t 1 t_1 t1 t 2 t_2 t2 的后缀,且不可能以其它形式出现。
  2. 对于任意两个子串 t 1 , t 2 ( ∣ t 1 ∣ ≤ ∣ t 2 ∣ ) t_1,t_2(|t_1|\le |t2|) t1,t2(t1t2)
    1. t 1 t_1 t1 t 2 t_2 t2 的后缀,则 e n d p o s ( t 2 ) ⊆ e n d p o s ( t 1 ) \mathrm{endpos}(t_2)\subseteq\mathrm{endpos}(t_1) endpos(t2)endpos(t1)
    2. 否则 e n d p o s ( t 1 ) ∩ e n d p o s ( t 2 ) = ∅ \mathrm{endpos}(t_1)\cap \mathrm{endpos}(t_2)=\emptyset endpos(t1)endpos(t2)=
  3. 在一个等价类中存的是一系列 长度连续 的串,并且它们 互为后缀。形式化地,假设这个等价类中的最长串为 S [ x … y ] S[x\dots y] S[xy],则其所有串为 S [ i … y ] ( x ≤ i ≤ z ) S[i\dots y](x\le i\le z) S[iy](xiz)。最长串长度为 m a x l e n = y − x + 1 \mathrm{maxlen}=y-x+1 maxlen=yx+1,最短串长度为 m i n l e n = y − z + 1 \mathrm{minlen}=y-z+1 minlen=yz+1

后缀 link

u u u 为一个非初始状态的状态。根据性质3,它对应一个等价类,这个等价类中的串都形如 S [ i … y ] ( x ≤ i ≤ z ) S[i\dots y](x\le i\le z) S[iy](xiz)。这相当于将长串 S [ x … y ] S[x\dots y] S[xy] 一个个“削去”首字母得到的串。而这些串有什么特性呢?根据性质1,它们之间互相 只以 后缀形式存在。

但是,在削到最短串 S [ z … y ] S[z\dots y] S[zy] 后,如果我继续削,得到的串的 e n d p o s \mathrm{endpos} endpos 就不同了(因为不属于一个等价类了)。根据性质2,它的 e n d p o s \mathrm{endpos} endpos 应该“扩增了”。这个新得到的串 对应的 e n d p o s \mathrm{endpos} endpos 对应的状态 v v v 叫作 u u u 的“父亲状态” (其实就是parent tree 上的父亲)。我们把 后缀 l i n k ( u ) \mathrm{link}(u) link(u) 定义为 v v v,即 u u u 的“父亲”。

于是我们发现,所有的后缀链接会构成一棵树。这个树叫 parent tree。

parent tree除了按后缀链接(自底向上)构造外,还可以理解为 endpos(自上而下)的分裂。

具体实现

以下不再区分 节点、状态 和 等价类,因为它们是一一对应的。

struct Node {
	int ch[26], fa, len;
}t[MAXN << 1];
int lst = 1, tot = 1;
void add_sam(int c) {
	int p = lst, np = lst = ++tot;
	t[np].len = t[p].len + 1;
	for(; p && !t[p].ch[c]; p = t[p].fa) t[p].ch[c] = np;
	if(!p) t[np].fa = 1;
	else {
		int q = t[p].ch[c];
		if(t[q].len == t[p].len + 1) t[np].fa = q;
		else {
			int nq = ++tot; t[nq] = t[q];
			t[nq].len = t[p].len + 1;
			t[q].fa = t[np].fa = nq;
			for(; p && t[p].ch[c] == q; p = t[p].fa) t[p].ch[c] = nq;
		}
	}
}

随意说两点吧,可能对理解有所帮助。

一、跳后缀link的意义:跳后缀link即为“压缩地”遍历当前串的全部后缀。如果从节点 p 开始跳,不妨设 p 对应的 等价类中最长串为 S [ x … y ] S[x\dots y] S[xy],那么跳后缀link即为 跳所有 S [ i … y ] ( x ≤ i ≤ y ) S[i\dots y](x\le i\le y) S[iy](xiy) 对应的节点。

二、为什么要遍历 旧串(即加入 c c c 之前的串)的所有后缀:加入 c c c 后我们考虑其对所有节点的endpos的影响。会影响的只会是旧串的后缀,它们都可以+c成为新串的一个后缀。

三、for(; p && !t[p].ch[c]; p = t[p].fa) t[p].ch[c] = np; 这句话的意义:在跳后缀link遍历 ((旧串)的所有后缀)时,如果没有转移边,说明这个等价类中的后缀连上一个c都不曾出现在旧串中出现过。那么现在多接了一个c,可以到达一个新的状态 np(因为这些串肯定是旧串的后缀,加一个c就是新串的后缀)。

四、if(t[q].len == t[p].len + 1) t[np].fa = q; 这句话的意义:说明 q 中的最长串是(p中的最长串+c),所以 q中的所有串都是新串的后缀,它们的endpos没有发生变化,都加入了一个 n n n。所以 q 仍然保持是一个节点。np 的后缀 link 也是 q,这是因为 q 是“(最长的)(不属于np等价类的)(新串的)后缀”。

五、这一段代码:非常重要

	int nq = ++tot; t[nq] = t[q];
    t[nq].len = t[p].len + 1;
	t[q].fa = t[np].fa = nq;
	for(; p && t[p].ch[c] == q; p = t[p].fa) t[p].ch[c] = nq;

由于不满足t[q].len == t[p].len + 1,说明 q 中分为两类串 t 1 t_1 t1 t 2 t_2 t2

  • t 1 t_1 t1 满足 ∣ t 1 ∣ = m a x l e n ( p ) + 1 |t_1|=\mathrm{maxlen}(p)+1 t1=maxlen(p)+1,同 四、 所述,它是新串的后缀,也应同 四、中一般处理。我们把它分裂出去形成一个新的等价类 nq。
  • t 2 t_2 t2 满足 ∣ t 2 ∣ > m a x l e n ( p ) + 1 |t_2|>\mathrm{maxlen}(p)+1 t2>maxlen(p)+1,它 不是 新串的后缀。那么它们的 endpos 就不能加 n n n

然后调整后缀link和转移即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值