关于 SAM 的一些证明
当(教练)让我推 SAM 的时候,我的心情是这样的:
我感觉会写不就行了。不管了,写几个证明吧。
在此之前,可以先看一下 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)\) 的。懒得证了,真的。