SA & SAM
SA & SAM
SA
我们想要求出一个字符串的所有后缀的排序 \(rk[]\) 以及排名是谁的字符串在哪里 \(sa[]\)。显然我们只需要求出来一个就可以了。
最简单的算法是 hash+二分 进行字符串比较,我们可以做到 \(O(n\log^2n)\),太慢了。
我们考虑一个倍增的思想。首先先对所有长度为 1 的子串进行排序,然后对长度为 2 的进行排序,这次排序可以看成是一个有两个关键字的排序,然后是长度为 4 的,以此类推。
然后我们基于这个想法,有一些常数上的优化,自己去看代码。
height 数组
\(height[i]=lcp(sa[i],sa[i-1])\),即第 \(i\) 名的后缀与它前一名的 lcp。\(height[1]=0\)。
\(LCP(sa[i],sa[j])=\min_{k=i+1}^jLCP(sa[k],sa[k-1])\)。
\(height[rk[i]]\ge height[rk[i-1]]-1\)。
这个东西说起来很抽象,但是我们抓住它的核心想法。我们现在就是想算 \(height[i]\),这是 \(lcp(sa[i],sa[i-1])\),这俩东西,如果第一位是相同的,那么我们事实上可以把第一位都删掉,然后想办法求剩下的 lcp。但是这俩东西事实上不一定相邻,没关系,我们先看一看把 \(sa[i]\) 删掉第一位之后剩下的 \(sa[i]+1\),它的 \(height\)。然后你发现其实我们应该是去更新后面这个的 \(height\),没关系,一样推。你设 \(height[i]\) 这个 lcp 事实上是 \(aA\),并且 \(sa[i]\) 是 \(aAC\),\(sa[i-1]\) 是 \(aAB\),那么 \(sa[i]+1\) 是 \(AC\),\(sa[i-1]+1\) 是 \(AB\),而 \(AB<AC\),它们俩的 lcp 已经是 \(height[i]-1\) 了,这就说明后面这个东西的 height 肯定比这个要大。
利用这个,我们按照 rk 的顺序暴力即可 \(O(n)\)。
SA 由于应用范围比 SAM 小,所以我们浅尝辄止。
SAM
SAM 是一个接受字符串 \(S\) 的所有后缀的最小 DFA。但是我们其实往往不会利用这个性质。
它的一条从起始节点 \(s\) 开始的路径跟一个 \(S\) 的子串一一对应。这自然导致一个子串跟一个节点对应,但一个节点可能会跟很多个子串对应。
我们定义 \(len(p),minlen(p),substr(p)\) 为你应该知道这是啥。
我们接下来介绍 SAM 中的几个重要概念。
endpos
定义一个 \(S\) 的子串 \(T\) 的 endpos 集合是指它每次出现的结束位置所构成的集合。关于 endpos,有如下性质。
两个串 \(u,v\) 的 endpos 集合,要么相互包含,要么不交。
不妨令 \(|u|\le|v|\)。如果 \(u\) 是 \(v\) 的后缀,那么显然有 \(endpos(v)\subseteq endpos(u)\)。
否则,如果 \(u\) 不是 \(v\) 的后缀,那么显然 \(endpos(v)\cap endpos(u)=\emptyset\)。
endpos 相等的集合构成等价类,并且这个等价类是由一段长度在 \([x,y]\) 内,连续不中断,每一个都是前一个的后缀的子串构成的。
首先,在同一个等价类的一定互相存在后缀关系。然后,我们证明若存在 \(|u|<|w|<|v|\),它们有后缀关系,并且 \(endpos(u)=endpos(v)\),那么 \(endpos(w)=endpos(u)\)。
\(endpos(v)\subseteq endpos(w)\subseteq endpos(u)\)。
同时,我们注意到,这个 endpos 集合等价类的关系,如果我们对包含关系连边的话,那么它会构成一棵树。事实上,这棵树就是我们后面后缀链接连出来的 parent tree。
link
定义一个状态的后缀链接 \(link(u)\) 是第一个不属于这个等价类的后缀所在的状态。我们假定初始状态 \(endpos(s_0)=\{-1,0,\dots,|S|\}\)。
我们知道这个结构构成一棵树。
然后我们阐述一些跟 link 没有关系的性质。
对于任意一个状态 \(p\),存在唯一的一个状态 \(q\),使得存在 \(q\to p\) 的转移,并且 \(len(q)+1=len(p)\)。并且也存在唯一一个 \(q\),使得 \(q\to p\),并且 \(minlen(q)+1=minlen(p)\)。
这个简单理解一下。
这样我们就可以定义 \(maxtrans(p),mintrans(p)\) 为上述的东西。
存在转移 \(p\to q\),说明 \(p\) 是 \(maxtrans(q)\) 到根节点的链上的一个点,说明 \(p\) 是 \(mintrans(q)\) 的子树内的一个点。
比较直观。
SAM 的实现
如果你理解上面的过程,这个其实是比较简单的。
这是一个在线增量构建的算法。我们的核心想法就是把新加入的所有以 \(n+1\) 为结尾的后缀找到它们应该在的状态并且丢进去。这个需要分三种情况讨论,我们不多赘述。
GSAM
好像比较神秘。但是总之我们现在有两种写法。一种是基于 dfs 的写法,一种是基于 bfs 的写法。我们去写 dfs。
https://www.cnblogs.com/Xing-Ling/p/12038349.html
https://www.luogu.com.cn/discuss/322224
reference
https://oi-wiki.org/string/sa/
https://www.cnblogs.com/alex-wei/p/Common_String_Theory_Theory_automaton_related.html
https://oi-wiki.org/string/sam/
https://oi-wiki.org/string/general-sam/
以及 sam visualizer https://mivik.gitee.io/sam-visualizer/

浙公网安备 33010602011771号