后缀数组入门

  • 基础介绍:

http://www.nocow.cn/index.php/%E5%90%8E%E7%BC%80%E6%95%B0%E7%BB%84

  • 应用:整理自《后缀数组——处理字符串的有力工具》

2.1、最长公共前缀

  这里先介绍后缀数组的一些性质。

  height数组:定义height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀。那么对于j和k,不妨设rank[j]<rank[k],则有以下性质:

  suffix(j)和suffix(k)的最长公共前缀为height[rank[j]+1],height[rank[j]+2],height[rank[j]+3],……,height[rank[k]]中的最小值。

  例如,字符串为“aabaaaab”,求后缀“abaaaab”和后缀“aaab”的最长公共前缀,如图4所示:

  那么应该如何高效的求出height值呢?

  如果按height[2],height[3],……,height[n]的顺序计算,最坏情况下时间复杂度为O(n2)。这样做并没有利用字符串的性质。定义h[i]=height[rank[i]],也就是suffix(i)和在它前一名的后缀的最长公共前缀。

  h数组有以下性质:

h[i]≥h[i-1]-1

  证明:

  设suffix(k)是排在suffix(i-1)前一名的后缀,则它们的最长公共前缀是h[i-1]。那么suffix(k+1)将排在suffix(i)的前面(这里要求h[i-1]>1,如果h[i-1]≤1,原式显然成立)并且suffix(k+1)和suffix(i)的最长公共前缀是h[i-1]-1,所以suffix(i)和在它前一名的后缀的最长公共前缀至少是h[i-1]-1。按照h[1],h[2],……,h[n]的顺序计算,并利用h数组的性质,时间复杂度可以降为O(n)。

  具体实现:

  实现的时候其实没有必要保存h数组,只须按照h[1],h[2],……,h[n]的顺序计算即可。代码:

int rank[maxn],height[maxn];

void calheight(int *r,int *sa,int n)

{

int i,j,k=0;

for(i=1;i<=n;i++) rank[sa[i]]=i;

for(i=0;i<n;height[rank[i++]]=k)

for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);

return;

}

  例1:最长公共前缀

  给定一个字符串,询问某两个后缀的最长公共前缀。

  算法分析:

  按照上面所说的做法,求两个后缀的最长公共前缀可以转化为求某个区间上的最小值。对于这个RMQ问题(如果对RMQ(Range Minimum Query)问题不熟悉,请阅读其他相关资料),可以用O(nlogn)的时间先预处理,以后每次回答询问的时间为O(1)。所以对于本问题,预处理时间为O(nlogn),每次回答询问的时间为O(1)。如果RMQ问题用O(n)的时间预处理,那么本问题预处理的时间可以做到O(n)。

2.2、单个字符串的相关问题

  这类问题的一个常用做法是先求后缀数组和 height数组,然后利用 height数组进行求解。

2.2.1、重复子串

  重复子串:字符串R在字符串L中至少出现两次,则称R是L的重复子串。

  例2:可重叠最长重复子串

  给定一个字符串,求最长重复子串,这两个子串可以重叠。

  算法分析:

  这道题是后缀数组的一个简单应用。做法比较简单,只需要求height数组里的最大值即可。首先求最长重复子串,等价于求两个后缀的最长公共前缀的最大值。因为任意两个后缀的最长公共前缀都是height数组里某一段的最小值,那么这个值一定不大于height数组里的最大值。所以最长重复子串的长度就是height数组里的最大值。这个做法的时间复杂度为O(n)。

  例3:不可重叠最长重复子串(pku1743)

  给定一个字符串,求最长重复子串,这两个子串不能重叠。

  算法分析:

  这题比上一题稍复杂一点。先二分答案,把题目变成判定性问题:判断是否存在两个长度为k的子串是相同的,且不重叠。解决这个问题的关键还是利用height数组。把排序后的后缀分成若干组,其中每组的后缀之间的height值都不小于k。例如,字符串为“aabaaaab”,当 k=2时,后缀分成了 4组,如图5所示。

  容易看出,有希望成为最长公共前缀不小于k的两个后缀一定在同一组。然后对于每组后缀,只须判断每个后缀的sa值的最大值和最小值之差是否不小于k。如果有一组满足,则说明存在,否则不存在。整个做法的时间复杂度为O(nlogn)。本题中利用 height值对后缀进行分组的方法很常用,请读者认真体会。

  例4:可重叠的 k次最长重复子串(pku3261)

  给定一个字符串,求至少出现k次的最长重复子串,这k个子串可以重叠。

  算法分析:

  这题的做法和上一题差不多,也是先二分答案,然后将后缀分成若干组。不同的是,这里要判断的是有没有一个组的后缀个数不小于k。如果有,那么存在k个相同的子串满足条件,否则不存在。这个做法的时间复杂度为 O(nlogn)。

2.2.2、子串的个数

  例5:不相同的子串的个数(spoj694,spoj705)

  给定一个字符串,求不相同的子串的个数。

  算法分析:

  每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同的前缀的个数。如果所有的后缀按照suffix(sa[1]),suffix(sa[2]),suffix(sa[3]),……,suffix(sa[n])的顺序计算,不难发现,对于每一次新加进来的后缀suffix(sa[k]),它将产生n-sa[k]+1个新的前缀。但是其中有height[k]个是和前面的字符串的前缀是相同的。所以suffix(sa[k])将“贡献”出n-sa[k]+1-height[k]个不同的子串。累加后便是原问题的答案。这个做法的时间复杂度为O(n)。

2.2.3、回文子串

  回文子串:如果将字符串L的某个子字符串R反过来写后和原来的字符串R一样,则称字符串R是字符串L的回文子串。

  例6:最长回文子串(ural1297)

  给定一个字符串,求最长回文子串。

  算法分析:

  穷举每一位,然后计算以这个字符为中心的最长回文子串。注意这里要分两种情况,一是回文子串的长度为奇数,二是长度为偶数。两种情况都可以转化为求一个后缀和一个反过来写的后缀的最长公共前缀。具体的做法是:将整个字符串反过来写在原字符串后面,中间用一个特殊的字符隔开。这样就把问题变为了求这个新的字符串的某两个后缀的最长公共前缀。如图6所示。

  这个做法的时间复杂度为O(nlogn)。如果RMQ问题用时间为O(n)的方法预处理,那么本题的时间复杂度可以降为O(n)。

2.2.4、连续重复子串

  连续重复串:如果一个字符串L是由某个字符串S重复R次而得到的,则称L是一个连续重复串。R是这个字符串的重复次数。

  例7:连续重复子串(pku2406)

  给定一个字符串L,已知这个字符串是由某个字符串S重复R次而得到的,求R的最大值。

  算法分析:

  做法比较简单,穷举字符串S的长 k,然后判断是否满足。判断的时候,先看字符串L的长度能否被k整除,再看suffix(1)和suffix(k+1)的最长公共前缀是否等于n-k。在询问最长公共前缀的时候,suffix(1)是固定的,所以RMQ问题没有必要做所有的预处理,只需求出height数组中的每一个数到height[rank[1]]之间的最小值即可。整个做法的时间复杂度为O(n)。

  例8:重复次数最多的连续重复子串(spoj687,pku3693)

  给定一个字符串,求重复次数最多的连续重复子串。

  算法分析:

  先穷举长度L,然后求长度为L的子串最多能连续出现几次。首先连续出现1次是肯定可以的,所以这里只考虑至少2次的情况。假设在原字符串中连续出现2次,记这个子字符串为S,那么S肯定包括了字符r[0],r[L],r[L*2],r[L*3],……中的某相邻的两个。所以只须看字符r[L*i]和r[L*(i+1)]往前和往后各能匹配到多远,记这个总长度为K,那么这里连续出现了K/L+1次。最后看最大值是多少。如图7所示。

  穷举长度L的时间是n,每次计算的时间是n/L。所以整个做法的时间复杂度是O(n/1+n/2+n/3+……+n/n)=O(nlogn)。

2.3、两个字符串的相关问题

  这类问题的一个常用做法是,先连接这两个字符串,然后求后缀数组和height数组,再利用height数组进行求解。

2.3.1、公共子串

  公共子串:如果字符串L同时出现在字符串A和字符串B中,则称字符串L是字符串A和字符串B的公共子串。

  例9:最长公共子串(pku2774,ural1517)

  给定两个字符串A和B,求最长公共子串。

  算法分析:

  字符串的任何一个子串都是这个字符串的某个后缀的前缀。求A和B的最长公共子串等价于求A的后缀和B的后缀的最长公共前缀的最大值。如果枚举A和B的所有的后缀,那么这样做显然效率低下。由于要计算A的后缀和B的后缀的最长公共前缀,所以先将第二个字符串写在第一个字符串后面,中间用一个没有出现过的字符隔开,再求这个新的字符串的后缀数组。观察一下,看看能不能从这个新的字符串的后缀数组中找到一些规律。以A=“aaaba”,B=“abaa”为例,如图8所示。

  那么是不是所有的height值中的最大值就是答案呢?不一定!有可能这两个后缀是在同一个字符串中的,所以实际上只有当suffix(sa[i-1])和suffix(sa[i])不是同一个字符串中的两个后缀时,height[i]才是满足条件的。而这其中的最大值就是答案。记字符串A和字符串B的长度分别为|A|和|B|。求新的字符串的后缀数组和height数组的时间是O(|A|+|B|),然后求排名相邻但原来不在同一个字符串中的两个后缀的height值的最大值,时间也是O(|A|+|B|),所以整个做法的时间复杂度为O(|A|+|B|)。时间复杂度已经取到下限,由此看出,这是一个非常优秀的算法。

2.3.2、子串的个数

  例10:长度不小于k的公共子串的个数(pku3415)

  给定两个字符串A和B,求长度不小于k的公共子串的个数(可以相同)。

  样例1:

  A=“xx”,B=“xx”,k=1,长度不小于k的公共子串的个数是5。

  样例2:

  A =“aababaa”,B =“abaabaa”,k=2,长度不小于k的公共子串的个数是22。

  算法分析:

  基本思路是计算A的所有后缀和B的所有后缀之间的最长公共前缀的长度,把最长公共前缀长度不小于k的部分全部加起来。先将两个字符串连起来,中间用一个没有出现过的字符隔开。按height值分组后,接下来的工作便是快速的统计每组中后缀之间的最长公共前缀之和。扫描一遍,每遇到一个B的后缀就统计与前面的A的后缀能产生多少个长度不小于k的公共子串,这里A的后缀需要用一个单调的栈来高效的维护。然后对A也这样做一次。具体的细节留给读者思考。

2.4、多个字符串的相关问题

  这类问题的一个常用做法是,先将所有的字符串连接起来,然后求后缀数组和height数组,再利用height数组进行求解。这中间可能需要二分答案。

  例11:不小于k个字符串中的最长子串(pku3294)

  给定n个字符串,求出现在不小于k个字符串中的最长子串。

  算法分析:

  将n个字符串连起来,中间用不相同的且没有出现在字符串中的字符隔开,求后缀数组。然后二分答案,用和例3同样的方法将后缀分成若干组,判断每组的后缀是否出现在不小于k个的原串中。这个做法的时间复杂度为O(nlogn)。

  例12:每个字符串至少出现两次且不重叠的最长子串(spoj220)

  给定n个字符串,求在每个字符串中至少出现两次且不重叠的最长子串。

  算法分析:

  做法和上题大同小异,也是先将n个字符串连起来,中间用不相同的且没有出现在字符串中的字符隔开,求后缀数组。然后二分答案,再将后缀分组。判断的时候,要看是否有一组后缀在每个原来的字符串中至少出现两次,并且在每个原来的字符串中,后缀的起始位置的最大值与最小值之差是否不小于当前答案(判断能否做到不重叠,如果题目中没有不重叠的要求,那么不用做此判断)。这个做法的时间复杂度为 O(nlogn)。

  例13:出现或反转后出现在每个字符串中的最长子串(PKU3294)

  给定n个字符串,求出现或反转后出现在每个字符串中的最长子串。

  算法分析:

  这题不同的地方在于要判断是否在反转后的字符串中出现。其实这并没有加大题目的难度。只需要先将每个字符串都反过来写一遍,中间用一个互不相同的且没有出现在字符串中的字符隔开,再将n个字符串全部连起来,中间也是用一个互不相同的且没有出现在字符串中的字符隔开,求后缀数组。然后二分答案,再将后缀分组。判断的时候,要看是否有一组后缀在每个原来的字符串或反转后的字符串中出现。这个做法的时间复杂度为O(nlogn)。

后缀数组入门,布布扣,bubuko.com

时间: 2024-10-05 23:09:11

后缀数组入门的相关文章

后缀数组入门(二)——Height数组与LCP

前言 看这篇博客前,先去了解一下后缀数组的基本操作吧:后缀数组入门(一)--后缀排序. 这篇博客的内容,主要建立于后缀排序的基础之上,进一步研究一个\(Height\)数组以及如何求\(LCP\). 什么是\(LCP\) \(LCP\),即\(Longest\ Common\ Prefix\),是最长公共前缀的意思. 而在后缀数组中,\(LCP(i,j)\)表示后缀\(_{SA_i}\)与后缀\(_{SA_j}\)的最长公共前缀的长度,注意是\(SA_i\)和\(SA_j\),而不是\(i\)和

hdu1403 后缀数组入门题

//1403 思路:字符串的任何一个子串都是这个字符串的某个后缀的前缀,则求A和B的最长公共子串等价于求A的后缀和B的后缀的最长公共前缀的最大值. 做法:将第二个字符串写在第一个字符串后面,中间用一个没有出现过的字符隔开,再求这个新的字符串的后缀数组. 1 #include "bits/stdc++.h" 2 using namespace std; 3 const int maxn = 1e6 + 10; 4 char str[maxn]; 5 int int_str[maxn];

poj 2774 Long Long Message(后缀数组入门题)

1 /****************************************************************** 2 题目: Long Long Message(poj 2774) 3 链接: http://poj.org/problem?id=2774 4 题意: 给两个字符串,找最长的公共子串 5 算法: 后缀数组 6 算法思想: 后缀数组就是套模板求先应得数组,这题用到了两个数组,分 7 别是sa[],height[];sa[i]表示所有后缀按字典数排序后以s[i

Long Long Message 后缀数组入门题

Long Long Message Time Limit: 4000MS   Memory Limit: 131072K Total Submissions: 22564   Accepted: 9255 Case Time Limit: 1000MS Description The little cat is majoring in physics in the capital of Byterland. A piece of sad news comes to him these days:

hdu4691(后缀数组)

算是后缀数组的入门题吧. 思路无比简单,要是直接套模板的话应该很容易秒掉. 关于后缀数组看高中神犇的论文就可以学会了 算法合集之<后缀数组——处理字符串的有力工具> 话说这题暴力是可以过了,但是我们在做多校的时候就是用暴力过的,当时还不知道什么是后缀数组... 靠着概念纯手敲了几个小时,把建SA,求height,和RMQ的ST算法都复习了一遍,这个东西要是每次都手敲的话真的会死人,尤其是倍增算法基数排序怎么排怎么别扭.自己写的倍增算法又太长,大牛的倍增算法总感觉敲的不顺. 贴个代码做留念...

[知识点]后缀数组

// 本文部分内容参照刘汝佳<算法竞赛入门经典训练指南>,特此说明. 1.前言 趁着这几天上午,把后缀数组大致看完了.这个东西本身的概念可能没太大理解问题,但是它所延伸出来的知识很复杂,很多,还有它的两个兄弟——后缀树,后缀自动机,编起来都不是盖的. 2.概念 前面曾经提到过Aho-Corasick自动机(http://www.cnblogs.com/jinkun113/p/4682853.html),讲得有点简略...它用以解决多模板匹配问题.但是前提是事先知道所有的模板,在实际应用中,我们

POJ 2774 后缀数组 || 二分+哈希

Long Long Message Time Limit: 4000MS   Memory Limit: 131072K Total Submissions: 35607   Accepted: 14275 Case Time Limit: 1000MS Description The little cat is majoring in physics in the capital of Byterland. A piece of sad news comes to him these days

[算法]后缀数组

前言 这篇博客真难写,暂定待更 定义 后缀数组[SuffixArray]是一个一维数组,简称SA,它保存1到n的某个排列\(SA[1] ,SA[2],\dots,SA[n]\),并且保证\(Suffix(SA[i])<Suffix(SA[i+1])\),\(1 \leq i < n\) .也就是将s的n个后缀按字典序从小到大进行排序之后把排好序的后缀的编号顺次放入SA中. 我们先从后缀数组的入门题洛咕P3809讲起 名词解释 后缀[suffix]: ? 类似前缀,在字符串处理中意为对于一个初始

『后缀自动机入门 SuffixAutomaton』

本文的图片材料多数来自\(\mathrm{hihocoder}\)中详尽的\(SAM\)介绍,文字总结为原创内容. 确定性有限状态自动机 DFA 首先我们要定义确定性有限状态自动机\(\mathrm{DFA}\),一个有限状态自动机可以用一个五元组\((\mathrm{S},\Sigma,\mathrm{st},\mathrm{end},\delta)\)表示,他们的含义如下: \(1.\) \(\mathrm{S}\) 代表自动机的状态集 \(2.\) \(\Sigma\) 代表字符集,也称字