SA
后缀
我们前面介绍的Border是因为前缀的后缀变为子串,所以我们需要Border
但后缀是什么?我们为什么要用?
不知道。
因为我们是无法描述SA。
0xFF 定义+SA定义
下文中的i若为下标则代表数值,否则代表\([i,n]\)的一个后缀
Suffix Array(后缀数组), 它是最难最难的一个算法。
\(sa_k\)为排名为k的后缀\([sa_i,n]\)
我们并不知道为什么我们需要用\(\mathbf{SA}\),所以我们先想我们如何求他
0x21 后缀排序
我们可以直接一个暴力\(sort\),时间为\(O(n\log n \times cmp)\)
其中cmp为比较时间,为\(O(n)\),所以直接暴力\(sort\)是\(O(n^2\log n)\)
我们发现在排序体系下,他的时间最短为\(O(n\log_2n)\)。
那暴力sort就为\(O(n^2logn)\)的下限吗?
不是!发现cmp可以用二分哈希,从而时间优化成\(O(n\log^2n)\)
代码超短,所以如果时间没有问题就二分哈希。
但模版是卡这个二分哈希的
接下来就是两条路:
(1)继续优化cmp函数
(2)跳出cmp。
很明显按照经验跳出cmp的思维难度较高,那如何优化cmp函数?
用SA
所以接下来的路只能用跳出cmp的方法了
关于O(n)的SA-IS笔者不会,但O(nlogn)的笔者就会了。
我们考虑\(rk_i\),为\(sa_{rk_i}=i\)的数组。
那么我们倍增,然后按照有了二元组\((rk_i,rk_{i+\frac{k}{2}+1})\),然后排序即可
代码有点小长,建议封装。
struct SuffixArray{
string s;
ll n;
vector<ll> sa, rk, h;
void build(string st){
s = st;
n = s.size()-1;
sa.resize(n+1);
rk.resize(n+1);
}
void buildSA(){
ll M = 200;
vector<ll> Tong(M, 0);
for(ll i = 1; i <= n; i++)Tong[s[i]]++;
for(ll i = 1; i < M; i++) Tong[i] += Tong[i-1];
for(ll i = n; i; i--) sa[Tong[s[i]]--] = i;
for(ll i = 1; i <= n; i++) rk[sa[i]] = rk[sa[i-1]] + (s[sa[i-1]] != s[sa[i]]);
for(ll p, k = 1; ; k <<=1){
p = 0;
vector<ll> id(n+1, 0);
for(ll i = n; i > n-k; i--) id[++p] = i;
for(ll i = 1; i <= n; i++)if(sa[i]>k) id[++p]=sa[i]-k;
vector<ll> Tong(M+1, 0);
for(ll i = 1; i <= n; i++) Tong[rk[i]]++;
for(ll i = 1; i <= M; i++) Tong[i] += Tong[i-1];
for(ll i = n; i; i--) sa[Tong[rk[id[i]]]--] = id[i];
auto ork = rk;
p = 0;
for(ll i = 1; i <= n; i++){
rk[sa[i]] =
(ork[sa[i]] == ork[sa[i-1]] &&
(sa[i-1]+k<=n?ork[sa[i-1]+k]:0) == (sa[i]+k <= n?ork[sa[i]+k]:0))?
p:++p;
}
if(p == n) break;
M = p;
}
}
}SA;
0x22 Height数组+后缀中的子串
我们在后面讲到Z函数时会着重定义lcp,若未知,请跳转到ex-kmp(实则没关系,看一眼lcp定义即可)
我们定义\(h_i=lcp(sa_i,sa_{i+1})\) 。
他有什么用?
定理2.1:本质不同子串个数为\(\frac{n(n+1)}{2}-\sum_{i=2}^n h_i\)
定理2.2:lcp(\(sa_i\),\(sa_j\))=\(\min_{k \in (i,j]}h_k\)
上文中的子串实际就代表了后缀数据结构拿出子串方式为$\mathbf{lcp} $
那剩下的问题如何做?
ST,Segment Tree,Tree chain partition and Fenwick Tree。
因为SA将string --转化-->segnele
所以剩下为序列问题,而非字符串问题.
浙公网安备 33010602011771号