网站开发平台论文,百度应用中心,官网最新版cmsv6,工业设计作品网站为什么学后缀数组 后缀数组是一个比较强大的处理字符串的算法#xff0c;是有关字符串的基础算法#xff0c;所以必须掌握。 学会后缀自动机(SAM)就不用学后缀数组(SA)了#xff1f;不#xff0c;虽然SAM看起来更为强大和全面#xff0c;但是有些SAM解决不了的问题能被SA解… 为什么学后缀数组 后缀数组是一个比较强大的处理字符串的算法是有关字符串的基础算法所以必须掌握。 学会后缀自动机(SAM)就不用学后缀数组(SA)了不虽然SAM看起来更为强大和全面但是有些SAM解决不了的问题能被SA解决只掌握SAM是远远不够的。 …… 有什么SAM做不了的例子 比如果求一个串后缀的lcp方面的应用这是SA可以很方便的用rmq来维护但是SAM还要求lca比较麻烦还有就是字符集比较大的时候SA也有优势。 现在这里放道题看完这个blog可能就会做了 你可想想这道题你有一个01串S然后定义一个前缀最右边的位置就是这个前缀的结束位置。现在有q多个询问每个询问结束位置在l~r中不同前缀的最长公共后缀是多长 |S|,q≤100000|S|,q≤100000 时限4s 而下面是我对后缀数组的一些理解 构造后缀数组——SA 先定义一些变量的含义 Str 需要处理的字符串(长度为Len) Suffix[i] Str下标为i ~ Len的连续子串(即后缀) Rank[i] : Suffix[i]在所有后缀中的排名 SA[i] : 满足Suffix[SA[1]] Suffix[SA[2]] …… Suffix[SA[Len]],即排名为i的后缀为Suffix[SA[i]] (与Rank是互逆运算) 好来形象的理解一下 后缀数组指的就是这个SA[i],有了它我们就可以实现一些很强大的功能(如不相同子串个数、连续重复子串等)。如何快速的到它便成为了这个算法的关键。而SA和Rank是互逆的只要求出任意一个另一个就可以O(Len)得到。 现在比较主流的算法有两种倍增和DC3在这里就主要讲一下稍微慢一些但比较好实现以及理解的倍增算法(虽说慢但也是O(Len logLen))的。 进入正题——倍增算法 倍增算法的主要思想 对于一个后缀Suffix[i],如果想直接得到Rank比较困难但是我们可以对每个字符开始的长度为2k2k的字符串求出排名k从0开始每次递增1(每递增1就成为一轮)当2k2k大于Len时所得到的序列就是Rank而SA也就知道了。O(logLen)枚举k 这样做有什么好处呢 设每一轮得到的序列为rank(注意r是小写最终后缀排名Rank是大写)。有一个很美妙的性质就出现了第k轮的rank可由第k - 1轮的rank快速得来! 为什么呢为了方便描述设SubStr(i, len)为从第i个字符开始长度为len的字符串我们可以把第k轮SubStr(i, 2k2k)看成是一个由SubStr(i, 2k−12k−1)和SubStr(i 2k−12k−1, 2k−12k−1)拼起来的东西。类似rmq算法这两个长度而2k−12k−1的字符串是上一轮遇到过的当然上一轮的rank也知道那么吧每个这一轮的字符串都转化为这种形式并且大家都知道字符串的比较是从左往右左边和右边的大小我们可以用上一轮的rank表示那么……这不就是一些两位数(也可以视为第一关键字和第二关键字)比较大小吗!再把这些两位数重新排名就是这一轮的rank。 我们用下面这张经典的图理解一下 相信只要理解字符串的比较法则(跟实数差不多)理解起来并不难。#还有一个细节就是怎么把这些两位数排序这种位数少的数进行排序毫无疑问的要用一个复杂度为长度*排序数的个数的优美算法——基数排序(对于两位数的数复杂度就是O(Len)的)。 基数排序原理 把数字依次按照由低位到高位依次排序排序时只看当前位。对于每一位排序时因为上一位已经是有序的所以这一位相等或符合大小条件时就不用交换位置如果不符合大小条件就交换实现可以用”桶”来做。(叙说起来比较奇怪看完下面的代码应该更好理解也可以上网查有关资料) 好了SA和Rank(大写R)到此为止就处理好了。(下面有详解代码)。但我们发现只有这两样东西好像没什么用为了处理重复子串之类的问题我们就要引入一个表示最长公共前缀的新助手Height数组 构造最长公共前缀——Height 同样先是定义一些变量 Heigth[i] : 表示Suffix[SA[i]]和Suffix[SA[i - 1]]的最长公共前缀也就是排名相邻的两个后缀的最长公共前缀 H[i] : 等于Height[Rank[i]]也就是后缀Suffix[i]和它前一名的后缀的最长公共前缀 而两个排名不相邻的最长公共前缀定义为排名在它们之间的Height的最小值。 跟上面一样先形像的理解一下 高效地得到Height数组 如果一个一个数按SA中的顺序比较的话复杂度是O(N2N2)级别的想要快速的得到Height就需要用到一个关于H数组的性质。 H[i] ≥ H[i - 1] - 1! 如果上面这个性质是对的那我们可以按照H[1]、H[2]……H[Len]的顺序进行计算那么复杂度就降为O(N)了 让我们尝试一下证明这个性质 : 设Suffix[k]是排在Suffix[i - 1]前一名的后缀则它们的最长公共前缀是H[i - 1]。都去掉第一个字符就变成Suffix[k 1]和Suffix[i]。如果H[i - 1] 0或1,那么H[i] ≥ 0显然成立。否则H[i] ≥ H[i - 1] - 1(去掉了原来的第一个,其他前缀一样相等)所以Suffix[i]和在它前一名的后缀的最长公共前缀至少是H[i - 1] - 1。 仔细想想还是比较好理解的。H求出来那Height就相应的求出来了这样结合SARank和Height我们就可以做很多关于字符串的题了 代码——Code 1 #include cstdio2 #include cstring3 #include algorithm4 using namespace std;5 const int N100010;6 int n,m,rank[N],sa[N],buc[N],id[N],height[N]; 7 char s[N];8 /*------------------------------------------------------------9 rank[i] 第i个后缀的排名;
10 sa[i] 排名为i的后缀位置;
11 buc[i] 计数排序辅助数组;
12 height[i] 排名为i的后缀与排名为(i-1)的后缀的LCP;
13 id[i] 倍增中后半段字符串位置(计数排序中的第二关键字);
14 s为原串
15 ------------------------------------------------------------*/
16 inline void qsort(){
17 //rank第一关键字,id第二关键字。
18 for(int i0;im;i) buc[i]0;
19 for(int i1;in;i) buc[rank[id[i]]];
20 for(int i1;im;i) buc[i]buc[i-1];
21 for(int in;i1;--i) sa[buc[rank[id[i]]]--]id[i];
22 //计数排序,把新的二元组排序
23 }
24 //通过二元组两个下标的比较确定两个子串是否相同
25 inline int cmp(int x,int y,int l){return id[x]id[y]id[xl]id[yl];}
26 int main() {
27 scanf(%s,s1);
28 nstrlen(s1);
29 for(int i1;in;i) rank[i]s[i],id[i]i;
30 m127,qsort();//一开始是以单个字符为单位所以(m 127)
31 for(int l1,p1,i;pn;l1,mp){
32 //l 当前一个子串的长度; m 当前离散后的排名种类数
33 //当前的id(第二关键字)可直接由上一次的sa的得到
34 //更新sa值,并用id暂时存下上一轮的rank(用于cmp比较)
35 for(p0,in-l1;in;i) id[p]i;//长度越界,第二关键字为0
36 for(i1;in;i) if (sa[i]l) id[p]sa[i]-l;
37 qsort(),swap(rank,id),rank[sa[1]]p1;
38 //用已经完成的SA来更新与它互逆的rank,并离散rank
39 for(i2;in;i) rank[sa[i]]cmp(sa[i],sa[i-1],l)?p:p;
40 }
41 //LCP 这个知道原理后就比较好理解程序
42 int j,k0;
43 for(int i1;in;height[rank[i]]k)
44 for(kk?k-1:k,jsa[rank[i]-1];s[ik]s[jk];k);
45 return 0;
46 } Code 4个比较基础的应用 Q1一个串中两个串的最大公共前缀是多少 A1这不就是Height吗用rmq预处理再O(1)查询。 Q2一个串中可重叠的重复最长子串是多长 A2就是求任意两个后缀的最长公共前缀而任意两个后缀的最长公共前缀都是Height 数组里某一段的最小值那最长的就是Height中的最大值。 Q3一个串种不可重叠的重复最长子串是多长 A3先二分答案转化成判别式的问题比较好处理。假设当前需要判别长度为k是否符合要求只需把排序后的后缀分成若干组其中每组的后缀之间的Height 值都不小于k再判断其中有没有不重复的后缀具体就是看最大的SA值和最小的SA值相差超不超过k有一组超过的话k就是合法答案。 A4一个字符串不相等的子串的个数是多少 Q4每个子串一定是某个后缀的前缀那么原问题等价于求所有后缀之间的不相同的前缀的个数。而且可以发现每一个后缀Suffix[SA[i]]的贡献是Len - SA[i] 1,但是有子串算重复重复的就是Heigh[i]个与前面相同的前缀那么减去就可以了。最后一个后缀Suffix[SA[i]]的贡献就是Len - SA[k] 1 - Height[k]。 对于后缀数组更多的应用这里就不详细阐述经过思考后每个人都会发现它的一些不同的用途它的功能也许比你想象中的更强大 最开始的那道题 先搬下来。。。 你可想想这道题你有一个01串S然后定义一个前缀最右边的位置就是这个前缀的结束位置。现在有很多个询问每q个询问结束位置在l~r中不同前缀的最长公共后缀是多长 |S|,q≤100000|S|,q≤100000 时限4s 简单思路首先可以把字符串反过来就是求后缀的最长公共前缀了可以用SA求出height数组然后用rmq预处理之后就是求两个位置间的最小值。然后对于一个区间显然只有在SA数组中相邻的两个串可以贡献答案。 对于区间询问的问题可以用莫队处理然后考虑加入一个后缀应该怎么处理我们可以维护一个按SA数组排序的链表。假设我们先把所有位置的SA全部加入然后按顺序删除重新按顺序加入时就可以O(1)完成修改。那么按照这个思路我们可以用固定左端点的并查集做到只加入不删除然后用O(nn−−√nlogn)O(nnnlogn)的复杂度完成这道题。 *可能后面的处理方式比较麻烦如果直接用splay维护区间中的后缀的话可以做到O(nn−−√logn)O(nnlogn)这个方法就比较直观而SAM在个问题上还是有点无力的。这题只是为了说明SA相比于SAM还是有他的独到之处特别是在处理后缀的lcp之类的问题上。 结束 转载于:https://www.cnblogs.com/Sparks-Pion/p/9558888.html