后缀数组
膜了一天,貌似有一点点感觉了?赶紧记下来,免得又忘了。。
$sa_i$是第$i$个后缀的开头在原串的位置。
$rk_i$是$S_{i...|S|}$这一段后缀的排名。
$ht_i$是排名为$i-1$和$i$的后缀的$Longest \; common \; prefix$(最长公共前缀)。
构建$sa$数组,咱膜的是这篇文章,诱导排序与SA-IS算法。
${\color{Red}I}nduced{\color{Red}S}ort$,即诱导排序。
所谓诱导排序,貌似是通过一些信息,获得一些额外的速度?
咱在最后再加一个$\#$,为一个$S$中不会出现且字典序最小的字符。
后缀类型:
对于每一个后缀$suffix(i)$,如果$suffix(i)>suffix(i+1)$,则$suffix(i)$为$L$型后缀;如果$suffix(i)<suffix(i+1)$,则$suffix(i)$为$S$型后缀。特殊的,$suffix(|S|)$为$S$型后缀。
记$t_i$为后缀$suffix(i)$的类型,我们发现$t_i$是可以递推出来的。
$t_i=\begin{cases}L & \text{ if } S_i > S_{i+1} \\ S & \text{ if } S_i < S_{i+1} \\ t_{i+1} & \text{ if } S_i = S_{i+1}\end{cases}$
后缀类型还有一个很好的性质可以帮助我们排序后缀,
对于两个后缀$A$,$B$,如果$A_0=B_0$且$A$是$L$型,$B$是$S$型,则$A<B$。
证明:设$A=abX , B=acY$,假设$a \neq b, a \neq c$。$\\\because t_A = L,t_B=S\\\therefore a<b,a>c\\\therefore c<a<b\\\therefore A<B$。如果$a=b,a=c$,那么我们可以把$A,B$的$a$都去掉。因为$a=b,a=c$,所以$t_A,t_B$都不变,又变成了第一种情况。
懒得写了。。
大概的流程如下:
确定$t$。
确定$LMS$。
诱导排序$LMS$。
对每一个$LMS$重命名,生成新串$S1$。
如果$S1_i \; is \; unique$ 直接计算$SA1$
否则,递归计算$SA1$。
利用$SA1$诱导排序$SA$。
$suffix(a,L,1)<suffix(a,L,2)<...<suffix(a,L,\infty)<suffix(a,S,\infty)<...<suffix(a,S,2)<suffix(a,S,1)$
板子
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 using namespace std; 6 template < int N > 7 struct SuffixArray { 8 int sa[N], rk[N], ht[N], t[N<<1], s[N<<1], p[N], cnt[N], cur[N]; 9 #define pushL(x) sa[cur[s[x]]++] = x 10 #define pushS(x) sa[cur[s[x]]--] = x 11 #define sort(v) fill_n(sa, n, -1); fill_n(cnt, m, 0); \ 12 for (int i = 0; i < n; ++i) ++cnt[s[i]]; \ 13 for (int i = 1; i < m; ++i) cnt[i] += cnt[i-1]; \ 14 for (int i = 0; i < m; ++i) cur[i] = cnt[i] - 1; \ 15 for (int i = n1 - 1; ~i; --i) pushS(v[i]); \ 16 for (int i = 1; i < m; ++i) cur[i] = cnt[i-1]; \ 17 for (int i = 0; i < n; ++i) if (sa[i] > 0 && t[sa[i]-1]) pushL(sa[i]-1); \ 18 for (int i = 0; i < m; ++i) cur[i] = cnt[i] - 1; \ 19 for (int i = n - 1; ~i; --i) if (sa[i] > 0 && !t[sa[i]-1]) pushS(sa[i]-1) 20 void sais(int n, int m, int *s, int *t, int *p) { 21 int n1 = t[n-1] = 0, ch = rk[0] = -1, *s1 = s + n; 22 for (int i = n - 2; ~i; --i) t[i] = s[i+1] == s[i] ? t[i+1] : s[i] > s[i+1]; 23 for (int i = 1; i < n; ++i) rk[i] = t[i-1] && !t[i] ? (p[n1] = i, n1++) : -1; 24 sort(p); 25 for (int i = 0, x, y; i < n; ++i) if (~(x = rk[sa[i]])) { 26 if (ch < 1 || p[x+1] - p[x] != p[y+1] - p[y]) ++ch; 27 else for (int j = p[x], k = p[y]; j <= p[x+1]; ++j, ++k) 28 if ((s[j]<<1|t[j]) != (s[k]<<1|t[k])) {++ch; break;} 29 s1[y=x] = ch; 30 } 31 if (ch + 1 < n1) sais(n1, ch + 1, s1, t + n, p + n1); 32 else for (int i = 0; i < n1; ++i) sa[s1[i]] = i; 33 for (int i = 0; i < n1; ++i) s1[i] = p[sa[i]]; 34 sort(s1); 35 } 36 template < class T > 37 inline int map(int n, const T *str) { 38 int m = *max_element(str, str + n); 39 fill_n(rk, m + 1, 0); 40 for (int i = 0; i < n; ++i) rk[str[i]] = 1; 41 for (int i = 0; i < m; ++i) rk[i+1] += rk[i]; 42 for (int i = 0; i < n; ++i) s[i] = rk[str[i]] - 1; 43 return rk[m]; 44 } 45 template < class T > 46 void init(int n, const T *str) { 47 int m = map(n, str); 48 sais(n, m, s, t, p); 49 for (int i = 0; i < n; ++i) rk[sa[i]] = i; 50 for (int i = 0, h = ht[0] = 0; i < n - 1; ++i) { 51 int j = sa[rk[i]-1]; 52 while (i + h < n && j + h < n && s[i+h] == s[j+h]) ++h; 53 if ((ht[rk[i]] = h)) --h; 54 } 55 } 56 }; 57 int main() { 58 return 0; 59 }
应用
1.最长公共前缀
Q:求$S$中$suffix(i)$与$suffix(j)的$LCP$。
A:$\min_{k=rk_i+1}^{rk_j}\{ht_k\}$。
2.可重叠的最长重复子串。
Q:求$S$中两个最长的相同子串,它们可以重叠。
A:$\max{ht_i}$。
3.不可重叠的最长重复子串。
Q:求$S$中两个最长的相同子串,它们不能重叠。
A:二分最长重复子串的长度$L$。如果存在$i,j$满足$\min_{k=i}^j\{{ht_k}\} \geqslant L$且$\max_{k=i}^j\{sa_k\}-\min_{k=i}^j\{sa_k\} \geqslant L$,那么就存在长度为$L$的不重叠的重复子串。
习题
Long Long Message
题意:两个字符串的最长公共子串的长度。
题解:把这两个字符串连起来求出后缀数组,答案为满足$sa_i$与$sa_{i-1}$在两个字符串中的最大的$ht_i$。
Musical Theme
题意:求出最长的相似的子串,且不能重叠。
题解:$s_i=s_{i+1}-s_i$,应用3。
Milk Patterns
题意:求至少出现k次的最长重复子串,可以重叠。
题解:二分子串长度,把$ht$分组。若有一组的后缀个数不小于k,即满足条件。
Distinct Substrings
题意:求字符串不同子串的数目。
题解:字符串不同子串的数目就是字符串所有后缀之间不相同的前缀的数目。利用$ht$求解。$suffix(sa_i)$对答案的贡献是$n-sa_i+1-ht_i$。
Palindrome
题意:求最长回文子串。
题解:把原串倒着放到原串的后面,中间用一个分隔符隔开,得到一个新串。求这个新串的两边的最长公共子串就行。具体的方法就是枚举回文中心。
关键代码如下: (query为求lcp的函数)
1 void solve(int m, int n) { 2 int len = 0, f, e; 3 for (int t, i = 0; i < m; ++i) { 4 if ((t = query(i, n - i)) * 2 > len) 5 len = t * 2, f = i - t, e = i + t - 1; 6 if ((t = query(i, n - i - 1)) * 2 - 1 > len) 7 len = t * 2 - 1, f = i - t + 1, e = i + t - 1; 8 } 9 for (int i = f; i <= e; ++i) 10 printf("%c", ::s[i]); 11 }
Power Strings
题意:给定一个字符串$L$,已知这个字符串是由某个字符串$S$重复$R$次而得到的,求$R$的最大值。
题解:枚举$S$的长度$k$,如果$k|\|L\| \wedge lcp(suffix(i), suffix(k+1))=n-k$,即成立。
Common Substrings
题意:求$A,B$长度不小于k的公共子串的个数。
题解:当然是先把$A$,$B$连起来,然后按照$ht \geqslant k$分组。考虑有一对$i,j$分属$A,B$,他们的贡献即为$lcp(i,j)-k+1$。
维护$A$做一次,维护$B$做一次。这是对称的,咱只写一种。
考虑$A$对$B$的贡献。在一组中,枚举到一个$B$时造成的贡献为$(lcp-k+1)*cnt$,其中$cnt$为本组之前$A$的个数。用单调栈维护一组中$ht$的最小值和某一$ht$的$A$的数目$cnt$。
Life Forms
题意:给出$n$个字符串,求出现次数$\geqslant \lfloor \frac{n}{2} \rfloor$的最长子串。如果有多个,按字典序输出。
题解:连在一起求出后缀数组。二分长度$L$。