【转载&总结】后缀数组及广泛应用
2017-08-03 22:05
357 查看
转自:http://blog.csdn.net/yxuanwkeith/article/details/50636898 五分钟搞懂后缀数组!后缀数组解析以及应用(附详解代码) 作者:YxuanwKeith
学会后缀自动机(SAM)就不用学后缀数组(SA)了?不,虽然SAM看起来更为强大和全面,但是有些SAM解决不了的问题能被SA解决,只掌握SAM是远远不够的。
……
有什么SAM做不了的例子?
比如果求一个串后缀的lcp方面的应用,这是SA可以很方便的用rmq来维护,但是SAM还要求lca,比较麻烦,还有就是字符集比较大的时候SA也有优势。
现在这里放道题,看完这个blog可能就会做了!:
你可想想这道题:你有一个01串S,然后定义一个前缀最右边的位置就是这个前缀的结束位置。现在有q多个询问,每个询问结束位置在l~r中不同前缀的最长公共后缀是多长?
|S|,q≤100000
时限4s
而下面是我对后缀数组的一些理解
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是互逆运算)
好,来形象的理解一下
神奇的代码
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
时限4s
简单思路:首先可以把字符串反过来就是求后缀的最长公共前缀了,可以用SA求出height数组,然后用rmq预处理之后就是求两个位置间的最小值。然后对于一个区间,显然只有在SA数组中相邻的两个串可以贡献答案。
对于区间询问的问题可以用莫队处理,然后考虑加入一个后缀应该怎么处理,我们可以维护一个按SA数组排序的链表。假设我们先把所有位置的SA全部加入,然后按顺序删除,重新按顺序加入时就可以O(1)完成修改。那么按照这个思路我们可以用固定左端点的并查集,做到只加入,不删除,然后用O(nn√+nlogn)的复杂度完成这道题。
*可能后面的处理方式比较麻烦,如果直接用splay维护区间中的后缀的话可以做到O(nn√logn),这个方法就比较直观,而SAM在个问题上还是有点无力的。这题只是为了说明SA相比于SAM还是有他的独到之处,特别是在处理后缀的lcp之类的问题上。
简要总结
后缀数据充分利用了先前的信息,使得效率有可观性地提高。
suddix[i]表示后缀开始位置为i的后缀字符串
rank[i]表示第i个后缀的排名
sa[i]表示排名第i的后缀位置
height[i]表示排名为i和i-1的后缀字符串的最长公共前缀长度
以下是个人关于几个应用的理解
1.求字符串中可重叠的最长公共子串
根据height定义很显然这个就是height中的最大值了。
2.求字符串中不可重叠的最长公共子串
这个我们需要二分答案再验证,我们要二分可能的公共子串长度k,然后按sa的顺序对height进行分组,使组内的height值都不小于k,然后对于某个组内我们只要考察该组内sa的最大值和最小值的差是否大于等于k(实际上就是这两个后缀的开头是否相差k从而避免重叠),有则k成立。
3.求字符串中可重叠K次的最长公共子串
这个我们跟2差不多,二分公共长度k分组,然后我们考察每个组内的后缀个数是否大于等于K,有则K成立。
4.求字符串中不相同的子串个数
每个子串必定是某个后缀的前缀,那问题就是求所有后缀中不相同的前缀的个数,我们从顺序sa[1],sa[2],sa[3],不难发现每加入一个suffix[sa[i]],它有n-sa[i]+1个前缀(就是这个后缀的长度),其中有height[i]是和前面的字符串相同(最长公共前缀嘛),所以这个字符串会贡献出n-sa[i]+1-height[i]不同的子串,累加后就可以了。
5.求字符串中最长的回文子串
所谓回文就是一个字符串满足中心对称,某个字符为对称中心,从这个字符向左和向右对应位置的字母都相等,如12345678987654321,我们设这个中心对称的字符为a[i],则我们就要判断 a[i-k]与a[i+k]是否相等,我们可以把整个字符串倒过来写在这个字符串后面(我们就得到了逆过来的那个12345678的字符串),其中加个特殊符号,这样我们可以简化判断,只用判断这新的字符串的某两个字符串的最长公共前缀
6.求字符串中连续的重复子串
已知一个字符串L是由某个字符串S重复R次得到的求最大值。
我们假设S的长度为k,首先L%k=0,然后判断suffix[1],和suffix[k+1]的最长公共子串是否为n-k。因此在查找最长公共子串的时候就是求height[rank[k+1]]到height[rank[1]]之间的最小值。因此我们的做法就是求height数组中每一个数到height[rank[1]]之间的数的最小值k,R=L的长度/K
7.求字符串中重复次数最多的连续重复子串
8.求两个字符串的最长公共子串
将这两个字符串连接起来,其中用一个特殊符号分开,然后再求出不在同一个字符串中的最大的height值即可。
9.求长度不小于K的最长公共子串
将两个字符串A、B连接起来,其中用一个特殊符号分开,然后用k对height数组分组,再统计每组的最长公共前缀和。每遇到一个B子串,就统计与前面A子串产生多少个长度不小于K的公共子串,这里A需要用栈来维护。然后对A也一样的处理。
10.求n个字符串的最长公共子串
这个用KMP可以处理,也可以将这n个字符串连成一个字符串,然后用不同的特殊符号分开,二分长度k对height数组进行分组判定是否该组中所有字符串的子串都出现在里面即可。
为什么学后缀数组
后缀数组是一个比较强大的处理字符串的算法,是有关字符串的基础算法,所以必须掌握。学会后缀自动机(SAM)就不用学后缀数组(SA)了?不,虽然SAM看起来更为强大和全面,但是有些SAM解决不了的问题能被SA解决,只掌握SAM是远远不够的。
……
有什么SAM做不了的例子?
比如果求一个串后缀的lcp方面的应用,这是SA可以很方便的用rmq来维护,但是SAM还要求lca,比较麻烦,还有就是字符集比较大的时候SA也有优势。
现在这里放道题,看完这个blog可能就会做了!:
你可想想这道题:你有一个01串S,然后定义一个前缀最右边的位置就是这个前缀的结束位置。现在有q多个询问,每个询问结束位置在l~r中不同前缀的最长公共后缀是多长?
|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是互逆运算)
好,来形象的理解一下
/* Problem: JZOJ1598(询问一个字符串中有多少至少出现两次的子串) Content: SA's Code and Explanation Author : YxuanwKeith */ #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MAXN = 100005; char ch[MAXN], All[MAXN]; int SA[MAXN], rank[MAXN], Height[MAXN], tax[MAXN], tp[MAXN], a[MAXN], n, m; char str[MAXN]; //rank[i] 第i个后缀的排名; SA[i] 排名为i的后缀位置; Height[i] 排名为i的后缀与排名为(i-1)的后缀的LCP //tax[i] 计数排序辅助数组; tp[i] rank的辅助数组(计数排序中的第二关键字),与SA意义一样! //a为原串 void RSort() { //rank第一关键字,tp第二关键字。 for (int i = 0; i <= m; i ++) tax[i] = 0;//计数初始化 for (int i = 1; i <= n; i ++) tax[rank[tp[i]]] ++; for (int i = 1; i <= m; i ++) tax[i] += tax[i-1];//前缀和求出排名 for (int i = n; i >= 1; i --) SA[tax[rank[tp[i]]] --] = tp[i]; //确保满足第一关键字的同时,再满足第二关键字的要求 } //计数排序,把新的二元组排序。 int cmp(int *f, int x, int y, int w) { return f[x] == f[y] && f[x + w] == f[y + w]; } //通过二元组两个下标的比较,确定两个子串是否相同 void Suffix() { //SA for (int i = 1; i <= n; i ++) rank[i] = a[i], tp[i] = i; m = 127 ,RSort(); //一开始是以单个字符为单位,所以(m = 127) for (int w = 1, p = 1, i; p < n; w += w, m = p) { //把子串长度翻倍,更新rank //w 当前一个子串的长度; m 当前离散后的排名种类数 //当前的tp(第二关键字)可直接由上一次的SA的得到 for (p = 0, i = n - w + 1; i <= n; i ++) tp[++ p] = i; //长度越界,第二关键字为0 for (i = 1; i <= n; i ++) if (SA[i] > w) tp[++ p] = SA[i] - w; //更新SA值,并用tp暂时存下上一轮的rank(用于cmp比较) RSort(), swap(rank, tp), rank[SA[1]] = p = 1; //用已经完成的SA来更新与它互逆的rank,并离散rank for (i = 2; i <= n; i ++) rank[SA[i]] = cmp(tp, SA[i], SA[i - 1], w) ? p : ++ p; } //离散:把相等的字符串的rank设为相同。 //LCP int j, k = 0; for(int i = 1; i <= n; Height[rank[i ++]] = k) for( k = k ? k - 1 : k, j = SA[rank[i] - 1]; a[i + k] == a[j + k]; ++ k); //这个知道原理后就比较好理解程序 } void Init() { scanf("%s", str); n = strlen(str); for (int i = 0; i < n; i ++) a[i + 1] = str[i]; } int main() { Init(); Suffix(); int ans = Height[2]; for (int i = 3; i <= n; i ++) ans += max(Height[i] - Height[i - 1], 0); printf("%d\n", ans); }
神奇的代码
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
时限4s
简单思路:首先可以把字符串反过来就是求后缀的最长公共前缀了,可以用SA求出height数组,然后用rmq预处理之后就是求两个位置间的最小值。然后对于一个区间,显然只有在SA数组中相邻的两个串可以贡献答案。
对于区间询问的问题可以用莫队处理,然后考虑加入一个后缀应该怎么处理,我们可以维护一个按SA数组排序的链表。假设我们先把所有位置的SA全部加入,然后按顺序删除,重新按顺序加入时就可以O(1)完成修改。那么按照这个思路我们可以用固定左端点的并查集,做到只加入,不删除,然后用O(nn√+nlogn)的复杂度完成这道题。
*可能后面的处理方式比较麻烦,如果直接用splay维护区间中的后缀的话可以做到O(nn√logn),这个方法就比较直观,而SAM在个问题上还是有点无力的。这题只是为了说明SA相比于SAM还是有他的独到之处,特别是在处理后缀的lcp之类的问题上。
结束
以上就是我对后缀数组的理解 ——YxuanwKeith简要总结
后缀数据充分利用了先前的信息,使得效率有可观性地提高。
suddix[i]表示后缀开始位置为i的后缀字符串
rank[i]表示第i个后缀的排名
sa[i]表示排名第i的后缀位置
height[i]表示排名为i和i-1的后缀字符串的最长公共前缀长度
以下是个人关于几个应用的理解
1.求字符串中可重叠的最长公共子串
根据height定义很显然这个就是height中的最大值了。
2.求字符串中不可重叠的最长公共子串
这个我们需要二分答案再验证,我们要二分可能的公共子串长度k,然后按sa的顺序对height进行分组,使组内的height值都不小于k,然后对于某个组内我们只要考察该组内sa的最大值和最小值的差是否大于等于k(实际上就是这两个后缀的开头是否相差k从而避免重叠),有则k成立。
3.求字符串中可重叠K次的最长公共子串
这个我们跟2差不多,二分公共长度k分组,然后我们考察每个组内的后缀个数是否大于等于K,有则K成立。
4.求字符串中不相同的子串个数
每个子串必定是某个后缀的前缀,那问题就是求所有后缀中不相同的前缀的个数,我们从顺序sa[1],sa[2],sa[3],不难发现每加入一个suffix[sa[i]],它有n-sa[i]+1个前缀(就是这个后缀的长度),其中有height[i]是和前面的字符串相同(最长公共前缀嘛),所以这个字符串会贡献出n-sa[i]+1-height[i]不同的子串,累加后就可以了。
5.求字符串中最长的回文子串
所谓回文就是一个字符串满足中心对称,某个字符为对称中心,从这个字符向左和向右对应位置的字母都相等,如12345678987654321,我们设这个中心对称的字符为a[i],则我们就要判断 a[i-k]与a[i+k]是否相等,我们可以把整个字符串倒过来写在这个字符串后面(我们就得到了逆过来的那个12345678的字符串),其中加个特殊符号,这样我们可以简化判断,只用判断这新的字符串的某两个字符串的最长公共前缀
6.求字符串中连续的重复子串
已知一个字符串L是由某个字符串S重复R次得到的求最大值。
我们假设S的长度为k,首先L%k=0,然后判断suffix[1],和suffix[k+1]的最长公共子串是否为n-k。因此在查找最长公共子串的时候就是求height[rank[k+1]]到height[rank[1]]之间的最小值。因此我们的做法就是求height数组中每一个数到height[rank[1]]之间的数的最小值k,R=L的长度/K
7.求字符串中重复次数最多的连续重复子串
8.求两个字符串的最长公共子串
将这两个字符串连接起来,其中用一个特殊符号分开,然后再求出不在同一个字符串中的最大的height值即可。
9.求长度不小于K的最长公共子串
将两个字符串A、B连接起来,其中用一个特殊符号分开,然后用k对height数组分组,再统计每组的最长公共前缀和。每遇到一个B子串,就统计与前面A子串产生多少个长度不小于K的公共子串,这里A需要用栈来维护。然后对A也一样的处理。
10.求n个字符串的最长公共子串
这个用KMP可以处理,也可以将这n个字符串连成一个字符串,然后用不同的特殊符号分开,二分长度k对height数组进行分组判定是否该组中所有字符串的子串都出现在里面即可。
相关文章推荐
- 常见数组&字符串API及其应用场景总结
- CF(427D-Match & Catch)后缀数组应用
- 【串和序列处理 5】总结---自动机,KMP算法,Extend-KMP,后缀树,后缀数组,trie树,trie图及其应用
- 数据库应用-后缀树及后缀数组(Suffix-Bäume&Suffix-Arraz)-1
- 数据库应用-后缀树及后缀数组(Suffix-Bäume&Suffix-Arraz)-2
- 转载:查找一段文字中最长的重复字串 - 编程珠玑(排过序的后缀数组的应用)
- 二维数组内的二分查找<转载>
- 第四章 数组和指针(part5) 总结 & 常用术语
- 后缀数组应用5: 求两个不同字串串的最长公共子串
- Java中“泛型之泛型类、泛型方法“和'泛型通配符"的应用总结
- Javascript 数组使用方法总结(转载)
- 后缀数组及其应用
- Struts2中关于"There is no Action mapped for namespace / and action name"的总结 (转载)
- CSU - 1551 Longest Increasing Subsequence Again —— 线段树/树状数组 + 前缀和&后缀和
- POJ3617:Best Cow Line (贪心&&后缀数组)
- bzoj 4278: [ONTAK2015]Tasowanie&bzoj 1692: [Usaco2007 Dec]队列变换 后缀数组+贪心
- cf244D. Match & Catch 字符串hash (模板)或 后缀数组。。。
- [转载]Java数组扩容算法及Java对它的应用
- 实验十三_编写、应用中断例程_2 & 总结
- (转载)Electron & C++ 快速开发桌面Web "混合"应用