以前一看到字符串的题目就有些畏惧,感觉无从下手,只会个KMP和AC自动机,很多情况下都是力不从心,所以打算学各种高端的算法,来解决这类问题,后缀数组应该是性价比教高的吧,至少很简单,容易理解,算法实现是另一回事,毕竟ACM比赛是可以带模版的,所以它也就显得简单实用了,下面一起看看这个实用的数据结构吧!
首先给定一些定义:
字符串S,s[ i ]表示第i个字符,s[ i , j ]表示第i个字符到第j个字符组成的子串, len表示S的长度
后缀, suffix( i )=s[ i, len-1] ,可见后缀是特殊的一类子串
后缀数组sa[ i ]表示将所有后缀按字典序排序后,排在第 i 位的后缀
名词数组rank[ i ],表示suffix(i)排在第几位,它是sa数组的逆运算,即rank[ sa[i] ]=i
高度数组height[ i ]表示 sa[ i ]与sa[ i-1 ]的最长公共前缀
后缀数组主要部分就是sa,rank 和 height这三个数组,整个结构相当简单,不过在构造后缀数组时,我们发现,最简单的方法往往是平方级的,很容易就超时,完全不可用,不过有两种方法来快速的构造后缀数组。
第一种方法:倍增思想 da
我们在普通排序的过程中忽略了后缀之间的联系,导致太多的重复比较,倍增的思想充分利用已经得到的结果,从而降低了整体复杂度
suffix(i)k表示长度为2^k的后缀,即s[ i, i+2^k ],我们可以很轻易的得到suffix(i)0的排序结果,而对于suffix(i)k的排序,我们可以通过suffix(i)k-1快速得到,suffix(i)k<suffix(j)k,当且仅当suffix(i)k-1<suffix(j)k-1||(suffix(i)k-1==suffix(j)k-1&&suffix(i+2^k-1)k-1<suffix(j+2^k-1)k-1),通过数学归纳法,我们可以知道这个过程是正确的,当2^k>len时,我们可以得到最终的排序结果,总排序次数为log len,当使用基数排序时,总复杂度是O(len*log len),基本上可以过掉大部分题
第二种方法: DC3
这种方法构思比较巧妙,不过同样也是充分利用前面的比较结果,这次我们将原字符串分成两部分,一部分为i mod 3=0的后缀,另一部分i mod 3!=0的后缀,我们可以发现,第一部分的结果容易通过第二部分的结果得到,而第二部分可以通过形成一个新的串,长度为2/3len,然后用同样的方式得到新串的排序结果,可以证明,所有串的总和为3len,所以复杂度自然为k*len,k表示常数。最后结果就是两部分归并后的结果
本来想写详细的解释的,发现时间越来越紧迫,等做些专题后再来整理吧,学习这两种方法主要看 《2004 年国家集训队论文 许智磊——后缀数组》主要介绍了理论和da算法,
《2005年集训队作业 周源——线性后缀排序算法》还有《2009年 国家集训队论文 罗穗骞——后缀数组——处理字符串的有力工具》前两篇注重理论,后一篇给出实现和一些习题
下面贴下我自己实现的模版,实现后发现和罗穗骞大神的差不多,我太弱了= =
模版:
template
struct suffixarray
{
int str[LEN*3],sa[LEN*3];
int rank[LEN],height[LEN];
int id[LEN];
int best[LEN][20];
int len;
bool equal(int *str, int a, int b)
{
return str[a]==str[b]&&str[a+1]==str[b+1]&&str[a+2]==str[b+2];
}
bool cmp3(int *str, int *nstr, int a, int b)
{
if(str[a]!=str[b])return str[a]=len||(i=k)y[j++]=sa[i]-k;
radixsort(x,y,sa,n,m);
swap(x,y);
/** 通过当前结果得到新字符串*/
x[sa[0]]=num++;
UPTO(i,1,n-1)
x[sa[i]]=cmp(y,sa[i-1],sa[i],k)?num-1:num++;
}
}
void initSA(T *s, int n,int m)
{
int i,j,k=0;
str[len=n]=0;//末尾增加一个0,这样就省去一些特殊情况的讨论,也就是最后一个mod 3刚好等于0
REP(i,n)str[i]=s[i];
dc3(str,sa,n+1,m);
REP(i,n)sa[i]=sa[i+1];//第0小的默认为最后一个字符0,所以答案向前移动一位,da算法不用
//da(str,sa,n,m);
REP(i,n)rank[sa[i]]=i;
REP(i,n)//计算height数组
{
if(k)--k;
if(rank[i])for(j=sa[rank[i]-1];str[i+k]==str[j+k];++k);
else k=0;
height[rank[i]]=k;
}
}
void initRMQ()
{
int i,j;
REP(i,len)best[i][0]=height[i];
for(j=1;(1<b)swap(a,b);
return RMQ(a,b);
}
};
相关习题:
poj 2774 Long Long Message 难度:1
这题求两个字符串的最长公共子串,算是模版题了
POJ 3261 Milk Patterns 难度:1
这题用到最长公共前缀,参考题解
poj 3294 Life Forms 难度:1
这题是上题的加强版吧,参考题解
poj 1226 Substrings 难度:2
这题跟3294差不多,不过这回加上回文,题解同上
poj 1743 Musical Theme 难度:2
楼教主男人八题,最长不重叠重复子串,参考题解
spoj 220 Relevant Phrases of Annihilation 难度:2
n个串的最长公共重复2次子串,参考题解
poj 3415 Common Substrings 难度:4
长度大于k的相同子串对数xian 后缀数组+单调桟统计,参考题解