最小循环表示法
求一个字符串的最小循环表示法的起始位置。
先把串倍长,维护两个指针(i,j)表示可能的最小循环表示的起始位置。
暴力求出(k=lcp(i,j)),然后比较(s_{i+k},s_{j+k})。
如果(s_{i+k}<s_{j+k}),那么([j,j+k])范围内的所有下标均不可能成为最小循环表示的起始位置,所以令(j=j+k+1)。
如果(s_{j+k}<s_{i+k}),那么([i,i+k])范围内的所有下标均不可能成为最小循环表示的起始位置,所以令(i=i+k+1)。
直到(i,j)有一个超过了(n)为止。
我们暴力求一次(k=lcp(i,j))的复杂度是(O(k))的,而(i,j)有一个会增加(k),因此时间复杂度是均摊(O(n))的。
Manacher算法
求出一个字符串中以各个位置为中心的最长回文子串的长度。
为了解决回文中心在两个字符中间的问题,我们在相邻两个字符间都插入一个$
。
记(len_i)表示以(i)为中心的最长回文子串的半径((i)自己算(1)),同时记录前缀中(i+len_i)的最大值(mx)与其下标(pos)。
考虑从左往右计算(len),假如当前的位置是(i),那么根据对称性我们知道(len_igemin(len_{pos*2-i},mx-i)),然后我们再暴力扩展(len_i)。这样我们就可以求出(len_i)了。
因为每次成功的暴力扩展一定会使(mx)向右移动,因此时间复杂度是均摊(O(n))的。
Knuth-Morris-Pratt算法
求出字符串(s)的(next)数组,(next_i)表示(pre_i)的最长border的长度。
考虑从左往右计算(next),假如当前的位置是(i),我们先令(k=next_{i-1}),然后判断(s_{k+1},s_i)是否相等,相等就有(next_i=k+1),否则就令(k=next_k)继续跳,直到相等或者跳到(0)为止。
因为一个位置的(next)被跳过之后就不会再被跳了,因此时间复杂度是均摊(O(n))的。
Some Applications
(1.)给定(s,t),求出(t)在(s)中的出现次数以及出现位置。
把暴力匹配时的往后跳(1)改成跳失配位置的(next)就行了,复杂度是均摊(O(n))的。
(2.)给定(s,t),求出(s)的每个(pre)在(t)中的出现次数。
先考虑(t=s)的情况,直接做似乎不太好做,我们考虑计算以每个位置(i)结尾有哪些(pre)出现过。
根据KMP的过程我们发现就是(i,next_i,next_{next_i-1},cdots)这些(pre)。
(i)我们单独抠出来最后算,其它的东西构成了一个树型结构,即(pre_k)出现的地方(pre_{next_k-1})一定会出现。
我们知道每个(pre_{next_i})都至少出现了一次,然后我们再自底向上(实际上就是从后往前)做个后缀和就好了。
然后考虑(t
e s)怎么做。先求出(s)的每个(pre)在(s)中的出现次数,再求出(s)的每个(pre)在s$t
中的出现次数,然后减一下就好了。这个过程的含义已经不言而喻了。
很显然,时间复杂度都是(O(n))。
Z-算法
给定字符串(s),求(s)与(t)每个(suf)的(lcp)。
先考虑(t=s)的情况,记(len_i)表示以(lcp(s,suf_i)),同时记录前缀中(i+len_i)的最大值(mx)与其下标(pos)。
考虑从左往右计算(len),假如当前的位置是(i),那么我们知道(len_igemin(len_{i-pos+1},mx-i)),然后我们再暴力扩展(len_i)。这样我们就可以求出(len_i)了。
因为每次成功的暴力扩展一定会使(mx)向右移动,因此时间复杂度是均摊(O(n))的。
然后考虑(t
e s)怎么做。求出(s)与s$t
每个(suf)的(lcp)就好了。
SA
求出(s)的每个(suf)的相对大小关系。
用(rank_i)表示(suf_i)的排名,(sa_i)表示排名为(i)的(suf)的起始位置。
SA-IS
(height)数组
定义(h_i=lcp(suf_{sa_i},suf_{sa_{i-1}}),h_1=0)。
我们可以得到一个引理:(h_{rank_i}ge h_{rank_{i-1}}-1)。
因此我们可以暴力扩展求出(h),复杂度为均摊(O(n))。
Some Applications
(1.)给定(s),求最小的从(s)首尾取字符得到的新串
如果首尾字符不同那么可以简单贪心。
如果相同的话我们比较当前串的顺序和逆序(即原串的某个(suf)和(pre)),然后根据结果选择。
因此我们可以求s$t
((t)是(s)的逆序)的SA,这样就可以比较(s)的任意两个前后缀的大小了。
时间复杂度(O(n))。
(2.)给定(s),求(lcp(suf_i,suf_j))
(lcp(suf_{sa_i},suf_{sa_j})=minlimits_{k=i+1}^jh_k)
利用(O(n)-O(1))RMQ可以做到(O(n)-O(1))。
(3.)给定(s),比较(s_{l,r})和(s_{L,R})
若(lcp(suf_l,suf_r)gemin(r-l+1,R-L+1)),那么说明一个串包含于另一个串,大小关系就是(r-l+1)和(R-L+1)的关系。否则直接比较(rank_l,rand_L)即可。
时间复杂度(O(n)-O(1))。
(4.)给定字符串(s),求本质不同的子串个数
考虑简单容斥,答案等于总数减重复数,即({n+1choose 2}-sumlimits_{i=1}^nh_i)。
时间复杂度(O(n))。
(5.)给定(s),求至少出现(k)次的最长子串
出现至少(k)次的子串就是(k)个(suf)的(lcp),显然取(rank)连续的一段是最优的。那么单调队列随便搞搞就完事了。
时间复杂度(O(n))。
(6.)给定(s),求不重叠的最长重复子串
先二分答案(k),我们知道(lcp(sa_i,sa_j)=minlimits_{k=i+1}^jh_k),因此我们把(sa)分成若干个连续段,满足对于一段([l,r]),有(forall iin(l,r],h_ige k)。这样每个连续段中的后缀之间的(lcp)就会(ge k)了,我们只需要查询是否存在某个连续段满足(maxlimits_{k=l}^r sa_k-minlimits_{k=l}^r sa_kge k)即可。
利用(O(n)-O(1))RMQ可以做到(O(nlog n))。
(7.)给定字符串(s),已知(s=t^r(rinmathbb Q)),求最大(r)
枚举(t)的长度(l),先判断是否有(lmid n),然后是否有(lcp(suf_1,suf_{l+1})=n-l)。
预处理出(h)数组中任意一个位置到(h_{rank_1})的最小值即可。
时间复杂度(O(n))。
(8.)给定(s),求(s)的子串中可以表示成(t^r(rin Q))的最大(r)以及对应的(t)
枚举(t)的长度(l),首先(t)至少会出现一次,所以我们只考虑(rge2)的情况。
很显然这个子串至少会包含(s_l,s_{2l},s_{3l},cdots)中相邻的两个。
那么我们看(s_{il},s_{(i+1)l})能够向前向后匹配多远,如果匹配的长度为(m),这里的重复次数就是(r=lfloorfrac ml
floor+1)。
相当于我们需要支持查询两个(suf)的(lcp)和两个(pre)的(lcs),正序倒序跑两边SA然后(O(n)-O(1))RMQ即可。
时间复杂度(O(nlog n))。实际上可以用Lyndon Array做到(O(n)),这里就不讲了。
(9.)给定(s,t),求(s,t)的(lcs)(最长公共子串)
求s$t
的SA,然后枚举(h_i),若(sa_{i-1},s_i)分别在s
部分和t
部分,那么(h_i)就是一个可能的(lcs),把所有可能的答案取个(max)就好了。
时间复杂度(O(n))。
(10.)给定(s,t),求长度(ge k)的(cs)(公共子串)个数
这里的(cs)定义为(s_{l,r}=t_{L,R})的((l,r,L,R))四元组。
先求s$t
的SA,那么我们要求的就是每个在t
部分的(sa_i)只经过(ge k)的(h_j)能够到达多少在s
部分的(sa_i)。开两个单调栈随便搞搞就完事了。
时间复杂度(O(n))。
(11.)给定(m)个字符串,求在至少(k)个字符串中出现的最长的串
先把所有串连起来,中间插一个$
得到(t)并求(t)的SA。
然后我们二分一下答案(r),像例题(6)中的做法一样把(sa)分成若干个连续段,然后判断是否有某一段中包含至少(k)个字符串部分的(suf),开个桶随便搞搞就完事了。
时间复杂度(O((sum n)log(sum n)))。
(12.)给定(m)个字符串,求在至少(k)个字符串中至少出现(2)次的不重叠的最长重复子串
例题(11)+例题(6)。
把(s)连起来得到(t)求出SA,二分答案(r),把(sa)分段,判断是否有某一段中包含了至少(k)个字符串部分的(suf)并且在这(k)个字符串部分的(max sa-min sage r)即可。
时间复杂度(O((sum n)log(sum n)))。
(13.)给定(m)个字符串,求自己本身或者自己的反转在至少(k)个字符串中都出现的最长子串
把每个串和自己的反转直接接起来然后跑例题(11)。
(14.)给定(s),求(s)的字典序第(k)小的子串
找到满足(t=sumlimits_{j=1}^{i-1}n+1-sa_j-h_j)的最大的(i),那么这个子串就是(s_{sa_i,sa_i+h_i+k-t-1})。
可以通过预处理+查询时二分做到(O(n)-O(log n))。