后缀数组的应用
后缀数组最详细讲解
后缀数组最详细讲解转载⾃后缀数组最详细(maybe)讲解后缀数组这个东西真的是神仙操作……但是这个⽐较神仙的东西在⽹上的讲解⼀般都仅限于思想⽽不是代码,⽽且这个东西开⼀堆数组,很多初学者写代码的时候很容易发⽣歧义理解,所以这⾥给出⼀个⽐较详细的讲解。
笔者⾃⼰也是和后缀数组硬刚了⼀个上午外加⼀个中午才理解的板⼦。
本⼈版权意识薄弱,如有侵权现象请联系博主邮箱xmzl200201@参考⽂献:以下是不认识的dalao们:特别感谢以下的两位dalao,写的特别好,打call什么是后缀数组我们先看⼏条定义:⼦串在字符串s中,取任意i<=j,那么在s中截取从i到j的这⼀段就叫做s的⼀个⼦串后缀后缀就是从字符串的某个位置i到字符串末尾的⼦串,我们定义以s的第i个字符为第⼀个元素的后缀为suff(i)后缀数组把s的每个后缀按照字典序排序,后缀数组sa[i]就表⽰排名为i的后缀的起始位置的下标⽽它的映射数组rk[i]就表⽰起始位置的下标为i的后缀的排名简单来说,sa表⽰排名为i的是啥,rk表⽰第i个的排名是啥⼀定要记牢这些数组的意思,后⾯看代码的时候如果记不牢的话就绝对看不懂后缀数组的思想先说最暴⼒的情况,快排(n log n)每个后缀,但是这是字符串,所以⽐较任意两个后缀的复杂度其实是O(n),这样⼀来就是接近O(n^2 log n)的复杂度,数据⼤了肯定是不⾏的,所以我们这⾥有两个优化。
ps:本⽂中的^表⽰平⽅⽽不是异或倍增⾸先读⼊字符串之后我们现根据单个字符排序,当然也可以理解为先按照每个后缀的第⼀个字符排序。
对于每个字符,我们按照字典序给⼀个排名(当然可以并列),这⾥称作关键字。
接下来我们再把相邻的两个关键字合并到⼀起,就相当于根据每⼀个后缀的前两个字符进⾏排序。
想想看,这样就是以第⼀个字符(也就是⾃⼰本⾝)的排名为第⼀关键字,以第⼆个字符的排名为第⼆关键字,把组成的新数排完序之后再次标号。
没有第⼆关键字的补零。
(全国百强校)广东华南师范大学附属中学高中信息技术资料:处理字符串的有力工具-后缀数组
f(n) = O(n) + f(2n/3) f(n) ≤ c×n + f(2n/3)
f(n)≤c×n+c×(2n/3)+c×(4n/9)+……≤3c×n 所以 f(n)=O(n)
由此看出,DC3算法是一个优秀的线性算法!
后缀数组的应用
例1:如果字符串L同时出现在字符串A
和字符串B中,则称字符串L是字符串A 和字符串B的公共子串。 给定两个字符串A和B,求最长公共子串。 例如:字符串“aaaba”和“abaa”的 最长公共子串为“aba”
DC3算法 复杂 难以实现
仅40行代码
DC3算法
(1)、先将后缀分成两部分,然后对 第一部分的后缀排序。
(2)、利用(1)的结果,对第二部分 的后缀排序。
(3)、将(1)和(2)的结果合并, 即完成对所有后缀排序。
(1)、将后缀分成两部分
字符的编号从0开始。 将后缀分成两部分: 第一部分是后缀k(k模3不等于0) 第二部分是后缀k(k模3等于0)
例1 最长公共子串
字符串的任何一个子串都是这个字符 串的某个后缀的前缀。求A和B的最长公 共子串等价于求A的后缀和B的后缀的最 长公共前缀的最大值。
将第二个字符串写在第一个字符串后 面,中间用一个没有出现过的字符隔开, 再求这个新的字符串的后缀数组。
如何高效的计算height数组?
如果按height[2],height[3],……, height[n]的顺序计算,最坏情况下时间复 杂度为O(n2)。这样做并没有利用字符串的 性质。定义h[i]=height[rank[i]],也就是 suffix(i)和在它前一名的后缀的最长公共前 缀。h数组有以下性质:
后缀树与后缀数组
• 显然, LCP(Suffix(i+1), Suffix(j+1)) = max(h[k]-1,0);
i i+1
j j+1
• 设i+1在sa中位置为t,sa[t+1] = p 即h[t] = LCP(suffix(i+1),suffix(p)) • 由suffix(i) < suffix (j) => suffix(i+1) < suffix(j+1) • 而suffix(p) 在sa数组中的位置紧贴着suffix(i+1),所以有 suffix(i+1) < suffix(p) <= suffix(j+1) • 而LCP(suffix(i+1),suffix(j+1)) = max(h[k]-1,0) 下标:1 2 3 4 5 Sa数组
– 当i超过字符串的长度,可以认为s[i] = -oo。
• 后缀:指从某个位置i开始到整个串结束的一个 特殊子串。字符串S的从i个字符开始的后缀记为 Suffix(i)。
– 显然,Suffix(i) = S[i..len(S)],记为S(i)
• 字符串的大小比较:例如串S与串T,从小到大 枚举i,如果s[i] < t[i] => S < T, 如果s[i] > t[i] => S > T。 两个串完全匹配则S== T
• 名次数组:名次数组Rank[i]保存的是 Suffix(i) 在所有后缀中从小到大排 列的“名次”。 可以视为大小
– 简单来说,名次数组就是问“你排第几”
• 显然,两者只要知道一个,就可以推出另外 一个
下标:1
2
后缀数组
OI笔记]后缀数组学习笔记--后缀数组解题方法总结2010-04-15 07:37后缀数组是处理字符串的有力工具。
后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现,能够实现后缀树的很多功能而时间复杂度也并不逊色,而且它比后缀树所占用的内存空间小很多。
可以说,后缀数组比后缀树要更为实用。
自从拜读了罗穗骞大牛的WC2009论文《后缀数组——处理字符串的有力工具》后,经过若干星期的努力(中间有因某些原因而缓下来),终于把论文上面的练习题全部完成了,现在写写自己对后缀数组的理解和感悟。
在看本笔记时,请不要忘记了,这是笔记,而教材是《后缀数组——处理字符串的有力工具》。
一:后缀数组的实现1、定义:Suffix Array数组(SA数组)用于保存从小到大排好序之后的后缀。
RANK名次数组用来保存后缀S[i..n]在所有后缀中是第几小的后缀。
简单来说,SA数组表示的是“排第几的是谁”,RANK数组表示的是“你的排名是多少”。
2、求SA数组以及RANK数组的方法:详细的请转到罗穗骞大牛的论文,我的学习笔记重点不是要介绍这个。
3、对DA(倍增算法)的一些个人理解:由于我只学习了倍增算法,所以我只能谈谈我对它的理解。
DC3算法我没有去研究....DA算法我是根据罗穗骞的模板写的,根据自己的理解做了些许的小优化。
我们现在来看看罗穗骞大牛的模板:int wa[maxn],wb[maxn],wv[maxn],ws[maxn];int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}void da(int *r,int *sa,int n,int m){int i,j,p,*x=wa,*y=wb,*t;for(i=0;i<m;i++) ws[i]=0;for(i=0;i<n;i++) ws[x[i]=r[i]]++;for(i=1;i<m;i++) ws[i]+=ws[i-1];for(i=n-1;i>=0;i--) sa[--ws[x[i]]]=i;for(j=1,p=1;p<n;j*=2,m=p){for(p=0,i=n-j;i<n;i++) y[p++]=i;for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;for(i=0;i<n;i++) wv[i]=x[y[i]];for(i=0;i<m;i++) ws[i]=0;for(i=0;i<n;i++) ws[wv[i]]++;for(i=1;i<m;i++) ws[i]+=ws[i-1];for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;}return;}其实,我个人认为,对于这个算法以及代码,无需过分深入地理解,只需记忆即可,理解只是为了帮助记忆罢了。
后缀数组的应用
二、后缀数组的应用本节主要介绍后缀数组在各种类型的字符串问题中的应用。
各题的原题请见附件二,参考代码请见附件三。
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]的顺序计算即可。
国家集训队2009论文集后缀数组——处理字符
后缀数组 罗穗骞
例 10:长度不小于 k 的公共子串的个数(pku3415) ……………23 2.4 多个字符串的相关问题 …………………………………………………23
例 11:不小于 k 个字符串中的最长子串(pku3294) ……………………24 例 12:每个字符串至少出现两次且不重叠的最长子串(spoj220)……24 例 13:出现或反转后出现在每个字符串中的最长子串(pku3294)……24 三、结束语 …………………………………………………………………………25 参考文献 ……………………………………………………………………………25 致谢 …………………………………………………………………………………25
目录
后缀数组 罗穗骞
摘要 …………………………………………………………………………………4 关键字 ………………………………………………………………………………4 正文 …………………………………………………………………………………4 一、后缀数组的实现 …………………………………………………………………4
符 串 r 的 从 第 i 个 字 符 开 始 的 后 缀 表 示 为 Suffix(i) , 也 就 是 Suffix(i)=r[i..len(r)]。
大小比较:关于字符串的大小比较,是指通常所说的“字典顺序”比较,也 就是对于两个字符串 u、v,令 i 从 1 开始顺次比较 u[i]和 v[i],如果 u[i]=v[i]则令 i 加 1,否则若 u[i]<v[i]则认为 u<v,u[i]>v[i]则认为 u>v (也就是 v<u),比较结束。如果 i>len(u)或者 i>len(v)仍比较不出结果,那 么 若 len(u)<len(v) 则 认 为 u<v , 若 len(u)=len(v) 则 认 为 u=v , 若 len(u)>len(v)则 u>v。
窗外一叶?后缀树与后缀数组
窗外一叶?后缀树与后缀数组本文系转载:/post/hj7cv6m后缀树和后缀数组简直就是ACM 选手必备的知识啊,我已经在两次比赛中碰到过相关的问题了。
我甚至还写过一篇应用的文章,可是我真是井底之蛙啊,那时我还不知道这个叫后缀数组,还有更好的构造算法,还有很多的应用。
最近终于好好在这方面扫了个盲,在此小小地总结一下。
假设有一个长度为 n 的字符串T[0 … n);S(i) 表示 T 的从下标 i 开始的后缀,即T[i … n)。
那么 T 的后缀数组就是把 S(i) ~ S(n – 1) 这n 个后缀按字典序排好序的一个数组。
它对于查找T 的子串之类的问题是很有用的。
问题就在于怎样快速地把这些后缀排好序。
最简单的方法就是把所有S(i) 快速排序。
快速排序本身的时间是O(n log n),但是由于排序的对象是一个个字符串,所以每次比较的时间在最差情况下都会变成线性的(也就是 O(n) 的),因此总的时间在最差情况下可能会升到O(n2) 左右,这就很慢了。
对此,我学到了三个更快的算法。
1. Ukkonen 算法Ukkonen 算法先用 O(n) 的时间构造一棵后缀树,然后再用 O(n) 的时间从后缀树得到后缀数组。
在这个网址,介绍了作者Esko Ukkonen,并列出了他的一些论文;其中的一篇《On-line construction of suffix-trees》是可以下载的,里面就讲解了什么是后缀树,怎样在 O(n) 的时间内构造它,以及怎样从它得到后缀数组。
不过我一开始还没发现这篇论文,我是从Dan Gusfield 的《Algorithms on Strings, Trees and Sequences –COMPUTER SCIENCE AND COMPUTATIONAL BIOLOGY》这本书里学到这个算法的。
这本书在中国没的卖,想买的话,可以找代购网站去Amazon 买。
我是在 eMule 上搜到并下载的。
后缀数组优化方法
后缀数组优化方法
4. 压缩后缀数组:在实际应用中,后缀数组可能会占用较大的内存空间。为了减少内存的 使用,可以采用压缩后缀数组的方法。具体而言,可以将后缀数组中的元素进行压缩,并使 用更少的空间来存储后缀的起始位置。
以上是一些常见的后缀数组优化方法。根据具体的问题和需求,可以选择适合的优化方法 来提高后缀数组的构建和使用效率。
后缀数组优化方法
后缀数组是一种用于解决字符串相关问题的数据结构,它可以高效地进行字符串匹配、最 长公共子串等操作。在构建后缀数组时,可以使用一些优化方法来提高算法的效率。以下是 一些常见的后缀数组优化方法:
1. 倍增法:倍增法是一种常用的构建后缀数组的方法。它通过不断扩大步长来进行迭代, 从而减少比较的次数。具体而言,倍增法首先将字符串中的字符按照ASCII码排序,然后根据Байду номын сангаас字符的排名构建后缀数组。在构建过程中,每次比较的步长会逐渐加倍,从而减少比较的次 数。
后缀数组优化方法
2. DC3算法:DC3算法是一种高效的后缀数组构建算法,它的时间复杂度为O(n),其中n 是字符串的长度。DC3算法的核心思想是将字符串划分为三个等长的子串,然后分别对这三 个子串构建后缀数组。通过递归地应用这个思想,最终可以得到完整的后缀数组。
3. Kasai算法:Kasai算法是一种用于构建后缀数组的优化方法,它可以在O(n)的时间复 杂度内计算出后缀数组中相邻后缀的最长公共前缀。这个最长公共前缀信息可以在后续的字 符串匹配和相关操作中发挥重要作用。
后缀数组及其应用
O(nlogn)
O(n)。
1、double_algorithm构造后缀数组;。。。。。O(nlogn) 2、线性计算出h[]数组,再逐个推出height[i];。。。O(n) 3、ST算法对height[]做预处理;。。。。。。。O(nlogn) 4、查询LCP(I,J)只需查询height[i…j]中的最小值 O(1)
关于RMQ问题(Range Minimum Query)
线段树等高级数据结构维护,O(nlogn)构造,O(logn) 查询,ST算法O(nlogn)构造,O(1)的查询。RMQ标准算法 O(n)构造,0(1)查询。
在信息竞赛中,均衡利弊还是ST实现简单,效率较高, 性价比高。 这样一来,我们可以在O(1)的时间内查询任意两个后缀的 最长公共前缀。这也是后缀数组最强大的功能之一。 貌似问题得到了解决,回顾刚才的过程,我们漏掉了一 个重要的过程——预处理排名相邻后缀 暴力枚举后缀再比较。时间显然超过了O(n)。我们希 望能在O(n)时间内解决这一问题
后缀数组的两种主流构造方法
倍增算法(Double Algorithm) O(nlogn)
三分算法(Difference Cover modulo 3 ) O(N)
倍增算法(Double Algorithm)
总体来说,倍增算法的思想与ST的思想差不多。将后缀 长度依次分为1,2,4,8,。。。,2^k进行排序。进行当 前排序时利用到上次的排序结果。
S=“BANANAS”。
BANANAS 的后缀树
BANANAS 的Trie
后缀树在处理字符串问题上有着得天独厚的空间优势和速 度优势,在最坏情况下, 后缀树的节点数也不会超过2N。主 流的构造方法是由Esko Ukkonen 于1995年发明的一种线 性构造法,理论时间复杂度为O(N)。非常优秀。
后缀数组 模板题
后缀数组模板题后缀数组是一种用于处理字符串的数组,其中每个元素都是原字符串的一个后缀。
在算法和数据结构中,后缀数组常用于解决字符串相关的问题,例如字符串匹配、最长重复子串等。
以下是一个使用后缀数组解决最长重复子串问题的模板题:题目描述:给定两个字符串s和p,找出s中最长也是p中的子串的长度。
如果不存在这样的子串,则返回0。
输入格式:输入的第一行包含一个字符串s,长度不超过100000。
输入的第二行包含一个字符串p,长度不超过100000。
输出格式:输出一个整数,表示s中最长也是p中的子串的长度。
示例:输入:abcdefgabcdfeg输出:4解释:在字符串s和p中,最长的公共子串是"efg",其长度为4。
算法思路:1. 构建后缀数组:首先将字符串s转换为后缀数组SA,可以使用KMP算法或Manacher算法。
2. 构建部分匹配数组PM:对于后缀数组SA中的每个元素,统计以该元素结尾的子串在p中出现的次数,保存在部分匹配数组PM中。
注意,PM的长度应为SA+1,因为我们需要包括空字符串。
3. 查找最长公共子串:遍历后缀数组SA中的每个元素,对于每个元素i,从PM[i]开始向前搜索,找到最长的公共子串的长度。
具体做法是,对于每个元素i,如果PM[i]不为0,则说明以i结尾的子串在p中出现过。
从PM[i]开始向前搜索,找到最长的公共子串的长度。
如果PM[i]为0,则说明以i结尾的子串在p中没有出现过,直接跳过该元素。
4. 返回结果:返回找到的最长公共子串的长度。
后缀数组 模版
后缀数组模版
后缀数组模版,提升字符串处理效率的利器。
在计算机科学领域,字符串处理是一个非常重要的课题。
而后缀数组作为一种
重要的数据结构,被广泛应用于字符串处理中,能够大大提升字符串处理的效率。
后缀数组是一个存储字符串所有后缀的数组,它能够快速地解决很多与字符串
相关的问题,比如最长公共子串、最长回文子串等。
通过构建后缀数组,我们可以在O(nlogn)的时间复杂度内解决这些问题,而且在实际应用中,后缀数组的效率往往比其他数据结构要高。
在实际应用中,后缀数组被广泛应用于字符串匹配、模式识别、基因序列分析
等领域。
比如在搜索引擎中,后缀数组可以用来加速字符串的匹配,提高搜索效率;在基因序列分析中,后缀数组可以用来寻找重复的基因序列,帮助科学家们更好地理解基因的结构和功能。
除此之外,后缀数组还可以被用来构建后缀树、解决最长公共前缀等问题,可
以说是一个非常强大的工具。
总的来说,后缀数组作为一种重要的数据结构,在字符串处理中起着举足轻重
的作用。
它能够大大提高字符串处理的效率,应用范围广泛,是计算机科学领域中的一把利器。
希望未来能够有更多的研究者投入到后缀数组的研究中,为其在实际应用中发挥更大的作用做出贡献。
后缀数组 百度百科
后缀数组开放分类:计算机、程序设计后缀数组在字符串处理当中,后缀树和后缀数组都是非常有力的工具,其中后缀树大家了解得比较多,关于后缀数组则很少见于国内的资料。
其实后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现,能够实现后缀树的很多功能而时间复杂度也不太逊色,并且,它比后缀树所占用的空间小很多。
可以说,在信息学竞赛中后缀数组比后缀树要更为实用。
因此在本文中笔者想介绍一下后缀数组的基本概念、构造方法,以及配合后缀数组的最长公共前缀数组的构造方法,最后结合一些例子谈谈后缀数组的应用。
基本概念首先明确一些必要的定义:字符集一个字符集∑是一个建立了全序关系的集合,也就是说,∑中的任意两个不同的元素α和β都可以比较大小,要么α<β,要么β<α(也就是α>β)。
字符集∑中的元素称为字符。
字符串一个字符串S是将n个字符顺次排列形成的数组,n称为S的长度,表示为len(S)。
S的第i个字符表示为S。
子串字符串S的子串S[i..j],i≤j,表示S串中从i到j这一段,也就是顺次排列S,S[i+1],...,S[j]形成的字符串。
后缀后缀是指从某个位置i开始到整个串末尾结束的一个特殊子串。
字符串S的从i开头的后缀表示为Suffix(S,i),也就是Suffix(S,i)=S[i..len(S)]。
关于字符串的大小比较,是指通常所说的“字典顺序”比较,也就是对于两个字符串u、v,令i从1开始顺次比较u和v,如果相等则令i加1,否则若u<v则认为u<v,u>v则认为u>v(也就是v<u),比较结束。
如果i>len(u)或者i>len(v)仍未比较出结果,那么若len(u)<len(v)则认为u<v,若len(u)=len(v)则认为u=v,若len(u)>len(v)则u>v。
从字符串的大小比较的定义来看,S的两个开头位置不同的后缀u和v进行比较的结果不可能是相等,因为u=v的必要条件len(u)=len(v)在这里不可能满足。
后缀数组的定义及实现
后缀数组的定义及实现1 后缀数组的定义1.1 后缀数组的相关概念(1) 文本串的后缀:对于符号表Σ上的长度为n的文本串T[1...n],文字串T的后缀是指从第i个字符开始到T的末尾所形成的子串T[i...n],1 ≤i ≤n。
(2) 子串:在长度为n的文字串T中,下标从i到j的连续的(j-i+1)个字符所组成的字符序列就是文字串T的一个长度为( j – i + 1)子串,其中1 ≤i ≤j ≤n。
(3) 后缀数组:后缀数组SA是T的所有后缀T0, T1, ... T n-1的一个字典序排序,即SA[i] = j表示后缀T j是字符串集合{T0, T1, ... T n-1}中第i个最小字符串。
排序后的后缀数组具有如下性质:T SA[0] < T SA[1] < …< T SA[n - 1]下图为后缀数组及其名次示例(T = acaaccg$)此图表示描述了了SA中所需要得到的SA[i]和Rank[i],其中Rank[i] = j表示T中的第i个后缀在所有后缀的字典排名为j。
SA[i] = j的两种表示理解方式:(1) 表示T j在字符串集合中排名为i (2) 表示排名为i 的后缀的Position。
1.2 后缀数组的相关数组解释为了理解后缀数组,我们需要理解以下两个数组的含义以及它们之间的相互关系:SA数组(Pos数组):SA数组是我们需要直接使用的数组,SA数组是FM-index的基础,后面的bwt正是在SA的基础上进行变化得到文本L。
在1.1中提到:SA[i] = j表示后缀T j是字符串集合{T0, T1, ... T n-1}中第i个最小字符串。
因此SA[i] = j的两种表示理解方式:(1)表示T j在字符串集合中排名(字典序排名)为i。
(2)表示排名为i的后缀的Position。
Rank数组:Rank数组是后缀数组的逆数组SA-1,即若SA[j] = i,则Rank[i] = j,其实质反映了T中的第i个后缀在所有后缀中的字典序排名为j。
后缀数组(SA)
后缀数组(SA)SA 的⽤处(经典题型):求LCP,LCS,本质不同⼦串数出现 xxx 次⼦串,字符串中不重叠地出现⾄少两次的最长串长度最长AA式⼦串,连续的若⼲相同⼦串(AABB式)⾸先说⼀下,本篇博客是本⼈在oi-wiki学习后缀数组是写下的笔记,更详细的证明等见后缀数组主要包含两个数组:sa[ ]和rk[ ]。
sa[i]表⽰所有后缀中字典序第i⼩的那个后缀。
rk[i]表⽰i后缀的字典序排名。
求后缀数组模板提交处:其中倍增求SA的代码如下(使⽤基数排序):#include <iostream>#include <algorithm>#include <cmath>#include <cstdio>#include <cstring>#include <string>#define N 2000010template<typename T> inline void read(T &x) {x = 0; char c = getchar(); bool flag = false;while (isdigit(c)) {if (c == '-') flag = true; c = getchar(); }while (isdigit(c)) {x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); }if (flag) x = -x;}using namespace std;char s[N];int sa[N], rk[N], oldrk[N], id[N], p, n, w, limi;int bin[N];int main() {scanf("%s", s + 1);n = strlen(s + 1);for (register int i = 1; i <= n; ++i) ++bin[rk[i] = s[i]];for (register int i = 1; i <= 300; ++i) bin[i] += bin[i - 1];for (register int i = n; i > 0; --i) sa[bin[rk[i]]--] = i;limi = 300;//注意是limi不是p,因为for⼀开始时不会执⾏limi = pfor (w = 1; w < n; w <<= 1, limi = p) {//处理idp = 0; //注意p = 0for (register int i = n; i > n - w; --i) id[++p] = i;//注意"i"for (register int i = 1; i <= n; ++i)if (sa[i] > w) id[++p] = sa[i] - w;//注意"sa[i] - w" 注意"sa[i]"//处理samemset(bin, 0, sizeof(bin));for (register int i = 1; i <= n; ++i) ++bin[rk[id[i]]];for (register int i = 1; i <= limi; ++i) bin[i] += bin[i - 1];for (register int i = n; i > 0; --i) sa[bin[rk[id[i]]]--] = id[i];//处理rk(去重)memcpy(oldrk, rk, sizeof(rk));p = 0; //注意p = 0for (register int i = 1; i <= n; ++i)//注意"i - 1" 注意"oldrk"rk[sa[i]] = (oldrk[sa[i]] == oldrk[sa[i - 1]] && oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]) ? p : ++p; //注意 + wif (p == n) break;//⼩优化:如果已经排好序了,就不⽤再排了。
字符串--后缀数组的运用
字符串--后缀数组的运⽤后缀数组的运⽤主要体现在三个数组的使⽤之中1.sa[i]=n;表⽰的是排名是第i名的是第n个后缀2.rank[n]=i;表⽰的是第n个后缀在排序中是排在第⼏位的,它和sa数组是相反的。
3.height[ ]表⽰的是相邻的两个后缀之间的最长的公共前缀。
这三种数组的运⽤是有很多种,先总结两种。
⼀,求出现⾄少k次的最长不重复⼦串这就是height[ ]最典型的运⽤。
⼀般的思路是⼆分总的字符串的长度,来寻找符合条件的height[ ]中的段落,⼀旦出现符合条件的段落就记录下来,同时输出就可以。
例题题意:给出n个字符串,要求你找出⾄少在(n)/2个字符串中出现过的最长字串。
思路;这是⼀道典型的使⽤height[ ]数组的题⽬,这⾥只需要将给出的字符串都合并起来,同时在每⼀个合并的地⽅加上⼀个从来都没有出现过的字符,避免在两个字符串相交的地⽅出现符合条件的字串。
同时在最后合并的最后加上⼀个没有出现过的最⼩的字符。
这是为了,在建⽴三个数组的时候更好操作。
所以这题我们只要对⼀个长度进⾏⼆分,然后在height[ ]数组中分段就好了。
那么我们选择什么长度呢,这⾥只要选择所有给出的串中最长的⼀个,显然这是很符合条件的。
那么下⾯就是代码#include<cstdio>#include<cstring>#include<algorithm>#include<iostream>#include<string>#include<vector>#include<stack>#include<bitset>#include<cstdlib>#include<cmath>#include<set>#include<list>#include<deque>#include<map>#include<queue>using namespace std;typedef long long ll;const double PI = acos(-1.0);const double eps = 1e-6;const int mod =1000000;const int maxn =140110;char s[1100];int str[maxn];int Rank[maxn],height[maxn];int sa[maxn],t[maxn],t2[maxn],c[maxn];int belong[maxn],visit[200];void build_sa(int * s,int n,int m){int i,*x = t,*y = t2;for(i = 0;i < m;i++)c[i] = 0;for(i = 0;i < n;i++)c[ x[i] = s[i] ]++;for(i = 1;i < m;i++)c[i] += c[i-1];for(i = n-1;i >= 0;i--) sa[--c[x[i]]] = i;for(int k = 1;k <= n;k <<= 1){int p = 0;for(i = n - k;i < n;i++) y[p++] = i;for(i = 0;i < n;i++) if(sa[i] >= k) y[p++] = sa[i] - k;for(i = 0;i < m;i++) c[i] = 0;for(i = 0;i < n;i++) c[ x[y[i]] ]++;for(i = 0;i < m;i++) c[i] += c[i-1];for(i = n-1;i >= 0;i--) sa[--c[x[y[i]]]] = y[i];swap(x,y);p = 1; x[sa[0]] = 0;for(i = 1;i < n;i++)x[sa[i]] = y[sa[i-1]] == y[sa[i]] && y[sa[i-1] + k] == y[sa[i] + k] ? p-1:p++;if(p >= n) break;m = p;}}void calheight(int * s,int n){int i,j,k = 0;for(i = 0;i < n;i++)Rank[sa[i]] = i;for(i = 0;i < n;i++){if(k) k--;int j = sa[Rank[i]-1];while(s[i+k] == s[j+k]) k++;height[Rank[i]] = k;}}int Judge(int n,int len,int num){int i,j,k;int cnt=0;memset(visit,0,sizeof(visit));visit[0]=1;if(!visit[belong[sa[0]]]){cnt++;visit[belong[sa[0]]]=1;}for(i=1;i<n;i++){if(height[i]<len){cnt=0;memset(visit,0,sizeof(visit));visit[0]=1;if(!visit[belong[sa[i]]]){cnt++;visit[belong[sa[i]]]=1;}}else{if(!visit[belong[sa[i]]]){cnt++;visit[belong[sa[i]]]=1;}}if(cnt>=num) return 1;}return 0;}void out(int n,int len,int num){int i,j,cnt=0;memset(visit,0,sizeof(visit));visit[0]=1;if(!visit[belong[sa[0]]]){cnt++;}visit[belong[sa[0]]]=1;for(i=1;i<n;i++){if(height[i]<len){if(cnt>=num){for(j=sa[i-1];j<sa[i-1]+len;j++) printf("%c",str[j]-20);printf("\n");}cnt=0;memset(visit,0,sizeof(visit));visit[0]=1;if(!visit[belong[sa[i]]]){cnt++;visit[belong[sa[i]]]=1;}}else{if(!visit[belong[sa[i]]]){cnt++;visit[belong[sa[i]]]=1;}}}if(cnt>=num){for(j=sa[n-1];j<sa[n-1]+len;j++)printf("%c",str[j]-20);printf("\n");}}int main(){int n;int flag=1;while(scanf("%d",&n)==1&&n){if(!flag) printf("\n");else flag=0;int i,j,k;int pos=0,cnt=1,left=0,right=0;memset(belong,0,sizeof(belong));for(i=1;i<=n;i++){scanf("%s",s);int num=strlen(s);right=max(right,num);for(j=0;j<num;j++){str[pos+j]=int(s[j])+20;belong[pos+j]=i;}str[pos+num]=cnt++;pos=pos+num+1;}str[pos]=0;build_sa(str,pos+1,150);calheight(str,pos+1);int max_x=0;while(left<=right){int mid=(left+right)>>1;if(Judge(pos+1,mid,n/2+1)){max_x=mid;left=mid+1;}else{right=mid-1;}}//cout<<max_x<<"....."<<endl;if(max_x==0){printf("?\n");}else{out(pos+1,max_x,n/2+1);}}return 0;}2.求出现k次或者是⾄少k次的最长可重叠⼦串这个问题和上⾯的问题就很类似,实际上差别就不会很⼤,上⾯的要求是不能重叠,⽽这⾥则是可以重叠的,那么反⽽这个类型是更加简单的。
国家集训队2004论文集_许智磊(后缀数组)
第 1 页 共 11 页
IOI2004 国家集训队论文 许智磊
基本概念
首先明确一些必要的定义: 字符集 一个字符 集Σ是 一个建立了 全序关系的集合,也就是说 ,Σ 中 的任意两个不同的元素 α 和 β 都可以比较大小,要么 α<β,要么 β<α(也就是 α>β) 。字符集Σ中的元素称为字符。 字符串 一个字符串 S 是将 n 个字符顺次排列形成的数组,n 称为 S 的 长度,表示为 len(S)。S 的第 i 个字符表示为 S[i]。 子串 字符串 S 的子串 S[i..j],i≤j,表示 S 串中从 i 到 j 这一段,也就 是顺次排列 S[i],S[i+1],...,S[j]形成的字符串。 后缀 后缀是指从某个位置 i 开始到整个串末尾结束的一个特殊子串。 字符串 S 的从 i 开头的后缀表示为 Suffix(S,i),也就是 Suffix(S,i)=S[i..len(S)]。 关于字符串的 大小比较,是 指通常所说 的“字 典顺序 ”比较 ,也就是对 于 两个字符串 u、v,令 i 从 1 开始顺次比较 u[i]和 v[i],如果 u[i]=v[i]则令 i 加 1, 否则若 u[i]<v[i]则认为 u<v,u[i]>v[i]则认为 u>v(也就是 v<u) ,比较结束。如 果 i>len(u) 或 者 i>len(v) 仍 比较 出 结 果 , 那么若 len(u)<len(v) 则 认为 u<v , 若 len(u)=len(v)则认为 u=v,若 len(u)>len(v)则 u>v。 从字符串的大小比较的定义来看,S 的两个开头位置不同的后缀 u 和 v 进行 比较的结果不可能是相等,因为 u=v 的必要条件 len(u)=len(v)在这里不可能满 足。 下面我们约定一个字符集Σ和一个字符串 S,设 len(S)=n,且 S[n]='$',也 就是说 S 以一个特殊字符'$'结尾,并且'$'小于Σ中的任何一个字符。除了 S[n] 之外,S 中的其他字符都属于Σ。对于约定的字符串 S,从位置 i 开头的后缀直 接写成 Suffix(i),省去参数 S。 后 缀 数 组 后 缀 数 组 SA 是 一 个 一 维 数 组 , 它 保 存 1..n 的 某 个 排 列 SA[1],SA[2],...SA[n] ,并 且 保证 Suffix(SA[i])<Suffix(SA[i+1]),1≤i<n。 也就是 将 S 的 n 个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入 SA 中。 名次数组 名次数组 Rank=SA-1,也就是说若 SA[i]=j,则 Rank[j]=i,不难 看出 Rank[i]保存的是 Suffix(i)在所有后缀中从小到大排列的“名次” 。
字符串匹配(三)----后缀数组算法
字符串匹配(三)----后缀数组算法⼀、什么是后缀数组: 字符串后缀Suffix指的是从字符串的某个位置开始到其末尾的字符串⼦串。
后缀数组 Suffix Array(sa)指的是将某个字符串的所有后缀按字典序排序之后得到的数组,不过数组中不直接保存所有的后缀⼦串,只要记录后缀的起始下标就好了。
⽐如下⾯在下⾯这张图中,sa[8] = 7,表⽰在字典序中排第9的是起始下标为7的后缀⼦串,这⾥还有⼀个⽐较重要的数组rank,rank[i] : sa[i]在所有后缀中的排名,⽐如rk[5]=0,表⽰后缀下标为5的⼦串在后缀数组中排第0个; rank数组与sa数组互为逆运算,rk[sa[i]]=i; 现在假如我们已经求出来了后缀数组,然后直接对已经排好序的后缀数组进⾏⼆分查找,这样就能匹配成功了,下⾯贴出代码:import java.util.Arrays;public class SuffixArrayTest {public static void main(String[] args) {match(); // 得到结果是5}static void match(){String s = "ABABABABB";String p = "BABB";Suff[] sa = getSa(s); // 后缀数组int l = 0;int r = s.length()-1;// ⼆分查找,nlog(m)while(r>=l){int mid = l + ((r-l)>>1);// 居中的后缀Suff midSuff = sa[mid];String suffStr = midSuff.str;int compareRes;// 将后缀和模式串⽐较,O(n);if (suffStr.length()>=p.length()) {compareRes = suffStr.substring(0, p.length()).compareTo(p);}else {compareRes = pareTo(p);}// 相等了输出后缀的起始位置if(compareRes == 0){System.out.println(midSuff.index);break;}else if (compareRes<0) {l = mid + 1;}else {r = mid - 1;}}}/*** 直接对所有后缀排序,因为字符串的⽐较消耗O(N),所以整体为N²log(N)* @param src* @return*/public static Suff[] getSa(String src){int strLength = src.length();// sa 即SuffixArray,后缀数组// sa 是排名到下标的映射,即sa[i]=k说明排名为i的后缀是从k开始的Suff[] suffixArray = new Suff[strLength];for (int i = 0; i < strLength; i++) {String suffI = src.substring(i); //截取后缀suffixArray[i] = new Suff(suffI, i);}Arrays.sort(suffixArray); //依据Suff的⽐较规则进⾏排序return suffixArray;}static class Suff implements Comparable<Suff>{String str; //后缀内容int index; //后缀的起始下标public Suff(String str, int index) {super();this.str = str;this.index = index;}@Overridepublic int compareTo(Suff o2) {return pareTo(o2.str);}@Overridepublic String toString() {return "Suff{"+"str='"+str+"\'"+",index="+index+"}";}}}⼆、倍增法 上⾯求后缀数组的⽅式时间复杂度为n²log(n),⼀般来说,时间复杂度只要达到了n平⽅级别都要想办法降低,于是就有⼀种叫做倍增法的⽅法来求后缀数组,基本思想就是: 1、先将每个字符排序得到sa,rank数组, 2、然后给每个字符增添⼀个字符,这样就变成了两个字符,最后⼀个字符⽆法增添字符,就需要处理好边界问题。
后缀数组——处理字符串的有力工具
后缀数组 罗穗骞
例 10:长度不小于 k 的公共子串的个数(pku3415) ……………23 2.4 多个字符串的相关问题 …………………………………………………23
例 11:不小 于 k 个字符串中的最长子串(pku3294) ……………………24 例 12:每个字符串至少出现两次且不重叠的最长子串(spoj220)……24 例 13:出现或反转后出现在每个字符串中的最长子串(pku3294)……24 三、结束语 …………………………………………………………………………25 3.1 总结 ………………………………………………………………………25 3.2 参考文献 …………………………………………………………………25 3.3 致谢 ………………………………………………………………………25
1.2倍增算法
对每个字符开始的长度为 2k 的子字符串进行排序,求出排名,即 rank 值 。k 从 0 开始,每次加 1,当 2k 大于 n 以后,每个字符开始的长度为 2k 的子字符串 便相当于所有的后缀。并且这些子字符串都一定已经比较出大小,即 rank 值中 没有相同的值,那么此时的 rank 值就是最后的结果。每一次排序都利用上次长 度为 2k-1 的字符串的 rank 值,那么长度为 2k 的字符串就可以用两个长度为 2k-1 的字符串的排名作为关键字表示,然后进行基数排序,便得出了长度为 2k 的字 符串的 rank 值。以字符串“aabaaaab”为例,整个过程如图 2 所示。其中 x、y 是表示长度为 2k 的字符串的两个关键字。
例 1:最长公共前缀 ……………………………………………………17 2.2 单个字符串的相关问题 …………………………………………………17
2.2.1 重复子串 ………………………………………………………17 例 2:可重叠最长重复子串 ………………………………………17 例 3:不可重叠最长重复子串(pku1743)…………………………18 例 4:可重叠的最长重复子串(pku3261)…………………………19
详细解析后缀数组(RMQ及LCP)
经典的RMQ (Range Minimum Query)问题!!!
➢线段树、排序树 —— O(nlogn)预处理 , O(logn)每次询问 ➢标准RMQ方法 —— O(n)预处理 , O(1)每次询问
后缀数组——辅助工具
采用一种“神奇的”方法,可以在O(n)时间内计算出height数组 采用标准RMQ方法在O(n)时间内进行预处理 之后就可以在常数时间内算出任何的LCP(i,j)
名次数组
后缀数组——构造方法
如何构造后缀数组?
把n个后缀当作n个字符串,按照普通的方法进行排序 —— O(n2)
低效的原因 —— 把后缀仅仅当作普通的、独立 的字符串,忽略了后缀之间存在的有机联系。
后缀数组——构造方法
u[1..k] ,len(u)≥k 对字符串u,定义uk =
u
,len(u)<k
LCP Theorem 对任何1≤i<j≤n LCP(i,j)=min{LCP(k-1,k) | i+1≤k≤j} 称j-i为LCP(i,j)的“跨度”,LCP Theorem意义为: 跨度大于1的LCP值可以表示成一段跨度等于1的LCP值的最小值
后缀数组——辅助工具
LCP(i,j)=3
4=LCP(i,i+1)
后缀是一种特殊的子串 从某个位置i开始到整个串的末尾结束 S的从i开头的后缀等价于S[i..len(S)]
后缀数组——定义和符号
约定一个字符集Σ 待处理的字符串约定为S,约定len(S)=n 规定S以字符“$”结尾,即S[n]=“$”
“$”小于Σ中所有的字符 除了S[n]=“$”之外,S的其他字符都属于Σ
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
二、后缀数组的应用本节主要介绍后缀数组在各种类型的字符串问题中的应用。
各题的原题请见附件二,参考代码请见附件三。
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 getHeight(){int i, j, k=0;for (i=0; i<n; i++) rank[sa[i]] = i; //ÿÿsaÿrankfor (i=0; i<n; i++) {if (k) k--;int j=sa[rank[i]-1];while (s[i+k] == s[j+k] ) k++;height[ rank[i]] = k;}}例1:最长公共前缀给定一个字符串,询问某两个后缀的最长公共前缀。
算法分析:按照上面所说的做法,求两个后缀的最长公共前缀可以转化为求某个区间上的最小值。
对于这个RMQ问题(如果对RMQ问题不熟悉,请阅读其他相关资料),可以用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值分组后,接下来的工作便是快速的统计每组中后缀之间的最长公共前缀之和。