一些概念和理解:
Suffix(i): 表示从i位置到字符串末尾这个子串,(后缀数组);
sa[i]:表示排名为i的子串从哪个位置开始,即排名为i的子串为Suffix(sa[i]);
rank[i]:表示在字符串上,第i位置在后缀数组中的排名是多少;
height[i]: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[k]]中的最小值;
关于最长公共前缀:
求两个后缀的最长公共前缀即为求height数组中某区间上的最小值,显然这个可以用rmq问题解决。O(nlogn)的时间预处理,O(1)的时间询问。
ps:后缀数组还有好多应用,详见 罗穗骞《后缀数组——处理字符串的有力工具》
模板:
倍增算法:
#define maxn 1000001int 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=0;i--) sa[--ws[x[i]]]=i; for(j=1,p=1;p =j) y[p++]=sa[i]-j; for(i=0;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 b) {t=a;a=b;b=t;} return(height[askRMQ(a+1,b)]);}
dc3:
#define maxn 1000003 //注意扩大#define F(x) ((x)/3+((x)%3==1?0:tb))#define G(x) ((x)=0;i--) b[--ws[wv[i]]]=a[i]; return;}void dc3(int *r,int *sa,int n,int m){ int i,j,*rn=r+n,*san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p; r[n]=r[n+1]=0; for(i=0;i b) {t=a;a=b;b=t;} return(height[askRMQ(a+1,b)]);}
POJ 1743
给点一个字符串,求最长重复子串,这两个子串不能重叠。
二分答案,判断是否存在长度为k的子串是相同的,且不重叠。在判定过程中用到一个很犀利的方法:给height数组分组,使得每组中height值都不小于k。这样判断是否存在就是判断某组中有没有两个sa[i], sa[j]的差值 >= k(也就是长度 >= k);
//#pragma comment(linker,"/STACK:327680000,327680000")#include#include #include #include #include #include #include #include #include #include #include //#include #include
POJ 3261
给一个字符串,求至少出现k次的最长重复子串,这k个子串可以重叠。
还是二分答案,给height分组,不过这里只需要判断一组中是否有>=k个元素。
//#pragma comment(linker,"/STACK:327680000,327680000")#include#include #include #include #include #include #include #include #include #include #include //#include #include
URAL 1297
给一个字符串,求最长回文串。
现将字符串反转加到原串后面,中间用一个从来没有出现过的字符隔开,求后缀数组;然后枚举每一位i,求以i为中心的回文串的长度。(len为原串的长度,n为加上反转以后字符串的长度)也就是求i(0 <= i < len)位置和n - i - 1位置的最大公共前缀长度(枚举的这个串长度为计数),或者i位置和n - i位置的最大公共前缀长度。
//#pragma comment(linker,"/STACK:327680000,327680000")#include#include #include #include #include #include #include #include #include #include #include //#include #include
POJ 2406
给一个字符串L,一直这个字符串是由某个字符串S重复R次得到的,求最大的R。
枚举S的长度k,判断Suffix(0)和Suffix(k)的最长公共前缀是否为n - k。(n%k == 0);
找Suffix(k) 和 Suffix(0)的最长公共前缀时可以预处理出每个位置到Rank[0]位置的最小height值。
ps:这题只能dc3过,倍增TLE,T_T
详见代码:
//#pragma comment(linker,"/STACK:327680000,327680000")#include#include #include #include #include #include #include #include #include #include #include //#include #include
POJ 2274 && URAL 1517
给两个字符串,求最长公共子串。
将两个串合并成一个串,中间用一个没有出现过的字符隔开。构造后缀数组,然后找height数组中的最大值height[i](这个最大值必须满足sa[i-1]和sa[i]不在同一个串内)
//#pragma comment(linker,"/STACK:327680000,327680000")#include#include #include #include #include #include #include #include #include #include #include //#include #include
POJ 3415