后缀数组题型讲解.docx
《后缀数组题型讲解.docx》由会员分享,可在线阅读,更多相关《后缀数组题型讲解.docx(21页珍藏版)》请在冰豆网上搜索。
后缀数组题型讲解
后缀数组、不重复子串DistinctSubstrings
题目大意:
给出一个字符串,问它的不重复子串有多少个。
两题是一样的,除了字符串长度..
分析:
用后缀数组可以轻松解决。
因为这个字符串的每个子串必然是某个后缀的前缀,先用后缀数组求出sa和height,那么对于sa[k],它有n-sa[k]个子串,其中有height[k]个是和上一个后缀重复的,所以要减去。
所以用后缀数组求解的时间复杂度是O(n),后缀数组要是用倍增算法是O(nlog2n),效率很高。
note:
wa了一次,主要原因是忘了a[n]=0这个关键的初值...
PS:
各位大牛对我的差劲的c++代码有什么看法可以尽管喷哈!
codes:
#include
#include
usingnamespacestd;
constlongmaxn=1010;
longwn[maxn],wa[maxn],wb[maxn],wv[maxn],a[maxn],sa[maxn],rank[maxn],height[maxn];
charr[maxn];
longcmp(long*r,longa,longb,longl){
returnr[a]==r[b]&&r[a+l]==r[b+l];
}
voidda(long*r,long*sa,longn,longm){
longi,j,p,*x=wa,*y=wb,*t;
for(i=0;ifor(i=0;ifor(i=1;ifor(i=n-1;i>=0;i--)sa[--wn[x[i]]]=i;
for(p=1,j=1;pfor(p=0,i=n-j;ifor(i=0;i=j)y[p++]=sa[i]-j;
for(i=0;ifor(i=0;ifor(i=1;ifor(i=n-1;i>=0;i--)sa[--wn[wv[i]]]=y[i];
for(t=x,x=y,y=t,x[sa[0]]=0,p=1,i=1;ix[sa[i]]=cmp(y,sa[i-1],sa[i],j)?
p-1:
p++;
}
return;
}
voidcalheight(long*r,long*sa,longn){
longi,j,k=0;
for(i=1;i<=n;i++){rank[sa[i]]=i;height[i]=0;}
for(i=0;ifor(k?
k--:
0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
return;
}
intmain(){
longt,i;
cin>>t;
while(t--){
cin>>r;
longn=strlen(r);
for(inti=0;i(r[i]);
a[n]=0;
da(a,sa,n+1,256);
calheight(a,sa,n);
longsum=0;
for(i=1;i<=n;i++)sum+=n-sa[i]-height[i];
cout<}
return0;
}
----------------------------------------------------------------------------------------------------------------------------
[后缀数组]最长重复子串
分析:
任何一个重复子串,必然是某两个后缀的公共前缀。
由于任何两个后缀的最长公共前缀是一段height中的最小值,所以最长的一个重复子串应该是height中的最大值。
note:
1、sa的代码还需要继续写啊--总是有几个地方容易错..特别注意那个for(i=n-1;i>=0;i--)以及cmp(y,sa[i-1],sa[i],j)。
2、w数组理解错误,开小了--实际应该开maxn。
结果re了一次..
#include
#include
usingnamespacestd;
constintmaxn=100010;
intw[maxn],wa[maxn],wb[maxn],wv[maxn],a[maxn],sa[maxn],rank[maxn],height[maxn];
intcmp(int*r,inta,intb,intl){
returnr[a]==r[b]&&r[a+l]==r[b+l];
}
voidda(int*r,int*sa,intn,intm){
inti,j,p,*x=wa,*y=wb,*t;
for(i=0;ifor(i=0;ifor(i=1;ifor(i=n-1;i>=0;i--)sa[--w[x[i]]]=i;
for(p=1,j=1;pfor(p=0,i=n-j;ifor(i=0;i=j)y[p++]=sa[i]-j;
for(i=0;ifor(i=0;ifor(i=1;ifor(i=n-1;i>=0;i--)sa[--w[wv[i]]]=y[i];
for(t=x,x=y,y=t,p=1,i=1,x[sa[0]]=0;ix[sa[i]]=cmp(y,sa[i-1],sa[i],j)?
p-1:
p++;
}
return;
}
voidcal(int*r,int*sa,intn){
inti,j,k=0;
for(i=1;i<=n;i++)rank[sa[i]]=i;
for(i=0;ifor(k?
k--:
0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
return;
}
intmain(){
intt,i,j;
cin>>t;
while(t--){
chars[maxn];
cin>>s;
intn=strlen(s);
for(i=0;i(s[i]);
a[n]=0;
da(a,sa,n+1,255);
cal(a,sa,n);
intmax=0,k=0;
for(i=1;i<=n;i++)
if(maxmax=height[i];
k=sa[i];
}
for(i=k,j=0;jcout<}
return0;
}
----------------------------------------------------------------------------------------------------------------------------
[后缀数组、最长重复不重叠子串]
题意:
给出一个旋律,用n个数字[1,88]表示其音符,问它最长的主题长度是多少。
一个旋律的主题是一段至少出现过两次的不重叠音乐片段。
所谓重复出现,包括一段音乐全体加上某个数后再次出现。
如12345和56789是同一个音乐片段。
主题长度至少为5.无解输出0。
分析:
若把整个旋律看成一个字符串,这个问题有点像求最长重复不重叠子串。
稍作思考不难发现,两段音乐片段相同时,这两段音乐片段相邻两数字的差一样。
如12345可以看做1111,56789也可以看做1111,所以这两段音乐相同。
看出这点,就不难解决本题了。
先将输入转化成两两相邻的数的差,再把整个数组看做一个字符串,则问题转化为求最长重复不重叠子串的长度,而答案是这个长度+1。
另外,第一个数字与上一个数的差不存在,我们可以将它设为一个很大的值,使得它对最终结果不影响。
要解决上述问题,可以使用后缀数组。
先二分答案,将问题转化为判定性问题,然后利用height数组来对后缀进行分组,贪心判断即可。
具体的可以参考罗穗骞的论文,我这里主要讲些细节。
1、使用后缀数组的话,数组中的元素应该>0,而直接做差会使数组中的元素变成负数,所以可以在做差后加上一个足够大的常数,来避免负数的出现。
2、二分答案要写好很不容易。
我现在还有点糊涂--
3、输入用scanf。
题目里也有这个提示...
codes:
#include
usingnamespacestd;
constintmaxn=20010;
intw[maxn],wa[maxn],wb[maxn],wv[maxn],a[maxn],x[maxn],sa[maxn],rank[maxn],height[maxn],n;
intcmp(int*r,inta,intb,intl){
returnr[a]==r[b]&&r[a+l]==r[b+l];
}
voidda(int*r,int*sa,intn,intm){
inti,j,p,*x=wa,*y=wb,*t;
for(i=0;ifor(i=0;ifor(i=1;ifor(i=n-1;i>=0;i--)sa[--w[x[i]]]=i;
for(p=1,j=1;pfor(p=0,i=n-j;ifor(i=0;i=j)y[p++]=sa[i]-j;
for(i=0;ifor(i=0;ifor(i=1;ifor(i=n-1;i>=0;i--)sa[--w[wv[i]]]=y[i];
for(t=x,x=y,y=t,p=1,i=1,x[sa[0]]=0;ix[sa[i]]=cmp(y,sa[i-1],sa[i],j)?
p-1:
p++;
}
return;
}
voidcal(int*r,int*sa,intn){
inti,j,k=0;
for(i=1;i<=n;i++)rank[sa[i]]=i;
for(i=0;ifor(k?
k--:
0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
return;
}
intcheck(intk){
intmax=0,min=maxn;
for(inti=1;i<=n;i++){
if(height[i]max=min=sa[i];
}
else
{
max=max>sa[i]?
max:
sa[i];
min=minmin:
sa[i];
if(max-min>=k)return1;
}
}
if(max-min>=k)return1;elsereturn0;
}
intBin_search(intl,intr){
intmid;
for(mid=(l+r)>>1;l<=r;mid=(l+r)>>1){
if(check(mid))l=mid+1;elser=mid-1;
}
if(check(mid))returnmid;elsereturn0;
}
intmain(){
scanf("%d",&n);
while(n){
inti,k;
for(i=0;ia[0]=500;
for(i=1;ia[n]=0;
da(a,sa,n+1,501);
cal(a,sa,n);
k=Bin_search(0,n/2)+1;
if(k<5)k=0;
printf("%d\n",k);
scanf("%d",&n);
}
return0;
}
----------------------------------------------------------------------------------------------------------------------------
[后缀数组、出现k次的重复子串]
题目大意:
给出n个数字组成的一个字符串,求最长的恰好出现k次的重复子串(可重叠)的字符串的长度。
分析:
后缀数组一个经典的应用。
先二分答案,然后分组。
只要某一组包含的后缀数量大于等于k,表示有解。
这个不难理解。
等完成了论文里面的练习之后,我再写个总结笔记吧。
深刻体会到后缀数组的强大....
note:
1、分组时,每次height[i]2、scanf()返回的值是成功读取了多少个数据,如果文件结束了,返回EOF。
一开始不知道这个,结果OLE==
codes:
#include
usingnamespacestd;
constintmaxn=20010;
intw[maxn*2],wa[maxn],wb[maxn],wv[maxn],sa[maxn],rank[maxn],height[maxn],a[maxn];
intn,m,k;
intcmp(int*r,inta,intb,intl){
returnr[a]==r[b]&&r[a+l]==r[b+l];
}
voidda(int*r,int*sa,intn,intm){
inti,j,p,*x=wa,*y=wb,*t;
for(i=0;ifor(i=0;ifor(i=1;ifor(i=n-1;i>=0;i--)sa[--w[x[i]]]=i;
for(p=1,j=1;pfor(i=n-j,p=0;ifor(i=0;i=j)y[p++]=sa[i]-j;
for(i=0;ifor(i=0;ifor(i=1;ifor(i=n-1;i>=0;i--)sa[--w[wv[i]]]=y[i];
for(t=x,x=y,y=t,x[sa[0]]=0,p=1,i=1;ix[sa[i]]=cmp(y,sa[i-1],sa[i],j)?
p-1:
p++;
}
return;
}
voidcal(int*r,int*sa,intn){
inti,j,k=0;
for(i=1;i<=n;i++)rank[sa[i]]=i;
for(i=0;ifor(k?
k--:
0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
return;
}
intcheck(intx){
inti,cnt;
for(i=1;i<=n;i++)
if(height[i]else
if(++cnt>=k)return1;
if(cnt>=k)return1;
return0;
}
intbin_search(intl,intr){
intmid;
for(mid=(r+l)>>1;l<=r;mid=(r+l)>>1)
if(check(mid))l=mid+1;elser=mid-1;
returnmid;
}
intmain(){
inti;
while(scanf("%d%d",&n,&k)!
=EOF){
m=0;
for(i=0;i{
scanf("%d",&a[i]);
a[i]++;
m=m>a[i]?
m:
a[i];
}
a[n]=0;
m++;
da(a,sa,n+1,m);
cal(a,sa,n);
intk=bin_search(0,n);
printf("%d\n",k);
}
return0;
}
----------------------------------------------------------------------------------------------------------------------------
[后缀数组、最长回文子串]
题目大意:
给出一个字符串,求它的最长回文子串。
分析:
这题数据规模不大(n<=1000),所以直接暴力可以解决。
不过如果数据规模大了,暴力就不行了。
这里介绍后缀数组的做法。
首先,枚举回文子串的中心所在位置。
这里要分回文串长度为奇数和偶数两种情况考虑。
这两个问题均可以转化为求一个后缀和一个倒着写的后缀的最长公共前缀。
具体地,将原串与反着写之后的原串相连,中间以一个特殊字符隔开。
这个特殊字符只要不是0号,不影响后缀的排序,就没有问题。
(不能是0是因为我的倍增算法要求除了字符串的最后一位以外,其它位不能为0,否则会出错)然后算出height数组。
两个后缀的最长公共前缀为两个后缀排序之后,它们之间的串的height值的最小值。
这个可以自己举个具体例子好好体会。
用st算法求解rmq问题即可。
notes:
1、st算法我写得实在是少--for循环的先后要注意,先循环j,再循环i。
2、我在写的时候,加了一个错误的判断条件,结果导致waontest22,以后加啥代码都要有严谨的逻辑才好加...
codes:
#include
#include
#include
usingnamespacestd;
constintmaxn=2010;
intn,w[maxn],wa[maxn],wb[maxn],wv[maxn],a[maxn],sa[maxn],rank[maxn],height[maxn],f[maxn][20];
intcmp(int*r,inta,intb,intl){
returnr[a]==r[b]&&r[a+l]==r[b+l];
}
voidda(int*r,int*sa,intn,intm){
inti,j,p,*x=wa,*y=wb,*t;
for(i=0;ifor(i=0;ifor(i=1;ifor(i=n-1;i>=0;i--)sa[--w[x[i]]]=i;
for(p=1,j=1;pfor(p=0,i=n-j;ifor(i=0;i=j)y[p++]=sa[i]-j;
for(i=0;ifor(i=0;ifor(i=1;ifor(i=n-1;i>=0;i--)sa[--w[wv[i]]]=y[i];
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;ix[sa[i]]=cmp(y,sa[i-1],sa[i],j)?
p-1:
p++;
}
return;
}
voidcal(int*r,int*sa,intn){
inti,j,k=0;
for(i=1;i<=n;i++)rank[sa[i]]=i;
for(i=0;ifor(k?
k--:
0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
return;
}
intnmax(inta,intb){
if(a>b)returna;elsereturnb;
}
intnmin(inta,intb){
if(a
}
voidrmq(int*rank,intn){
inti,j;
memset(f,127,sizeof(f));
for(i=1;i<=n;i++)f[i][0]=height[i];
for(j=1;j<20;j++)
for(i=1;i+(1<f[i][j]=nmin(f[i][j-1],f[(1<<(j-1))+i][j-1]);
return;
}
intget_rmq(intx,inty){
inta=rank[x],b=rank[y];
if(a>b){intt=a;a=b;b=t;}
a++;
intt=int(log(double(b-a+1))/log(2.00));
returnnmin(f[a][t],f[b-(1<}
intmain(){
chars[maxn];
cin