关于 SAM 的一些证明

当(教练)让我推 SAM 的时候,我的心情是这样的:

image

我感觉会写不就行了。不管了,写几个证明吧。

在此之前,可以先看一下 this

首先 SAM 是一个只接受所有后缀的 DFA。

  • 状态 \(u\) 对应的字符串长度是一个区间。

在状态 \(u\) 的串的出现次数为 \(\mid \texttt{endpos(u)}\mid\)。这些串是后缀关系,随着长度减小,出现次数只能增,所以是一段区间。

  • endpos 要么无交要么包含。

如果两个 endpos 的交有东西,这个交的 \(len\) 区间不能有交,所以一个一定是另一个的后缀(最大 \(len\) 小于另一个最小 \(len\))。

  • 一个非叶节点有至少 \(2\) 个儿子。

endpos 是真子集关系,显然。

  • parent tree 最多 \(n\) 个叶子节点,总共最多 \(2n-1\) 个节点。

endpos 等价类最多一共有 \(2n-2\) 种。首先,每一个 endpos 等价类长度至少为 \(1\)。其次,一个等价类分裂成的 endpos 不会有元素重合。所以,最多分裂 \(n-1\) 次,一次分裂最多产生 \(2\) 个 endpos,于是就有 \(1+2\times (n-1)=2n-1\)。加上 \(t0\),就是 \(2n-1\)\(n\) 个叶子节点是因为长度只有最多 \(n\)

关于分裂 举一个例子:假设有字符串 $\tt{abc}$。节点 $1$ (空串)的 endpos 为 $\{1,2,3\}$,而这个 endpos 会分裂成 $\{1\},\{2\},\{3\}$,分别对应下一个是 $\tt{a,b,c}$ 的情况。本质上一个 endpos 的分裂就是假如下一个字符的时候的不同情况。
  • SAM 转移数量 \(\le 3n-4\)

由代码可知。

  • SAM 是一个 DAG。

\(t=trans(s,c)\)\(f=fa(s)\)

  • \(len(s)<len(t)\)

多了 \(c\),显然变大了。

  • \(trans(s,c)\neq null\implies trans(f,c)\neq null\)

因为 \(endpos(s)\in endpos(f)\)

  • \(len\) 的连续性可知 \(u\) 对应的字符串长度是 \([len(fa(u))+1,len(u)]\)

感觉现在凡是智商正常的都会 SAM 的代码了。贴一下。

void ins(int c){
	int p=lst,np=++tot;
	t[np].len=t[p].len+1;
	dp[np]=1;
	lst=np;
	while (p && !t[p].ch[c]){
		t[p].ch[c]=np;
		p=t[p].fa;
	}
	if (!p){
		t[np].fa=1;
		return;
	}
	int q=t[p].ch[c];
	if (t[q].len==t[p].len+1){
		t[np].fa=q;
		return;
	}
	int nq=++tot;
	t[nq]=t[q];
	t[nq].len=t[p].len+1;
	t[q].fa=t[np].fa=nq;
	while (p && t[p].ch[c]==q){
		t[p].ch[c]=nq;
		p=t[p].fa;
	}
}

构建的时间复杂度是 \(\mathcal{O}(n)\) 的。懒得证了,真的。

posted @ 2024-03-19 15:31  SFlyer  阅读(33)  评论(2编辑  收藏  举报