后缀数组(长度不小于k的公共子串的个数)

来源:互联网 发布:thinkphp nginx 配置 编辑:程序博客网 时间:2024/04/30 02:12

POJ 3415 Common Substrings
题意:长度不小于k的公共子串的个数;
思路:基本思路是计算A的所有后缀和B的所有后缀之间的最长公共前缀的长度,把最长公共前缀长度不小于k的部分全部加起来。 先将两个字符串连起来, 中间用一个没有出现过的字符隔开。按height值分组后,接下来的工作便是快速的统计每组中后缀之间的最长公共前缀之和。 扫描一遍, 每遇到一个B的后缀就统计与前面的A的后缀能产生多少个长度不小于k的公共子串, 这里A的后缀需要用一个单调的栈来高效的维护。 然后对A也这样做一次;
在这里提一下如何用单调栈维护,假如现在是维护A的后缀,找到一个A的后缀就将其入栈,但是如果将要入栈的这个的height值比栈里面的小则需要更新栈里面的的值,假如栈里面现在有两个值,分别为4 5,现在有一个值为3的要进栈,则需要将前两个的值更新为3,因为如果后面有B的后缀出现,则B与前面的A的后缀的最长公共前缀必定不会大于3;所以要更新为3,但是这样栈里就会有3个相同的值都为3,因此给每个值赋予一个权值代表里面有几个则可以批量更新,当遇到B后缀的时候记得也要维护一次单调栈,因为到B如果height值更小了,则结果将更小,维护完之后统计一下就好了;

/*** 倍增算法(n*logn)* 待排序数组长度为n,放在0~n-1中,在最后补0* sa为后缀数组,把后缀从小到大排序把后缀开头存起来,rank为名次数组,以i开头的后缀在所有后缀中排第几* sa的有效值为1~n,sa[0]必为n无效* rank的有效值为0~n-1,rank[n]必为0无效* height的有效值为2~n,前两个为0**/#include<cstdio>#include<cstring>#include<cstdlib>#include<iostream>#include<algorithm>#define F(x) ((x)/3+((x)%3==1?0:tb))#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)using namespace std;const int maxn=1e6+10;int x[maxn*3];int wa[maxn*3],wb[maxn*3],ww[maxn*3],wv[maxn*3],nn,Z=1,kk;char str[100010];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)//求的数组,得到的后缀数组,最长长度+1,数组里的最大值(一般180或者255);{    int i,j,p,*x=wa,*y=wb,*t;    for(i=0; i<m; i++) ww[i]=0;    for(i=0; i<n; i++) ww[x[i]=r[i]]++;    for(i=1; i<m; i++) ww[i]+=ww[i-1];    for(i=n-1; i>=0; i--) sa[--ww[x[i]]]=i; //处理长度为一的字符串,得到sa数组    for(j=1,p=1; p<n; j*=2,m=p) //倍增法求sa    {        for(p=0,i=n-j; i<n; i++) y[p++]=i;        for(i=0; i<n; i++) if(sa[i]>=j) y[p++]=sa[i]-j;//利用上次的sa直接求出按第二个关键字排序        for(i=0; i<n; i++) wv[i]=x[y[i]]; //第二关键字的排序得出第一关键字的顺序        for(i=0; i<m; i++) ww[i]=0;        for(i=0; i<n; i++) ww[wv[i]]++;        for(i=1; i<m; i++) ww[i]+=ww[i-1];        for(i=n-1; i>=0; i--) sa[--ww[wv[i]]]=y[i]; //根据第一关键字的顺序排出sa数组的顺序        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1; i<n; i++) //更新x数组 x为rank数组            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;    }    return ;}int c0(int *r,int a,int b){    return r[a]==r[b]&&r[a+1]==r[b+1]&&r[a+2]==r[b+2];}int c12(int k,int *r,int a,int b){    if(k==2) return r[a]<r[b]||(r[a]==r[b]&&c12(1,r,a+1,b+1));    else return r[a]<r[b]||(r[a]==r[b]&&wv[a+1]<wv[b+1]);}void Sort(int *r,int *a,int *b,int n,int m){    int i;    for(i=0; i<n; i++) wv[i]=r[a[i]];    for(i=0; i<m; i++) ww[i]=0;    for(i=0; i<n; i++) ww[wv[i]]++;    for(i=1; i<m; i++) ww[i]+=ww[i-1];    for(i=n-1; i>=0; i--) b[--ww[wv[i]]]=a[i];    return ;}void dc3(int *r,int *sa,int n,int m)//dc3比倍增法快一点{    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<n; i++) if(i%3!=0) wa[tbc++]=i;    Sort(r+2,wa,wb,tbc,m);    Sort(r+1,wb,wa,tbc,m);    Sort(r,wa,wb,tbc,m);    for(p=1,rn[F(wb[0])]=0,i=1; i<tbc; i++)        rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;    if(p<tbc) dc3(rn,san,tbc,p);    else for(i=0; i<tbc; i++) san[rn[i]]=i;    for(i=0; i<tbc; i++) if(san[i]<tb) wb[ta++]=san[i]*3;    if(n%3==1) wb[ta++]=n-1;    Sort(r,wb,wa,ta,m);    for(i=0; i<tbc; i++) wv[wb[i]=G(san[i])]=i;    for(i=0,j=0,p=0; i<ta&&j<tbc; p++)        sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];    for(; i<ta; p++) sa[p]=wa[i++];    for(; j<tbc; p++) sa[p]=wb[j++];    return ;}int h[maxn*3];//也就是排名相邻的两个后缀的最长公共前缀sa[i]和sa[i-1]int Rank[maxn*3];//名次数组void get_height(int *r,int *sa,int n)//同上,n小1{    int k=0,i,j;    for(int i=1; i<=n; i++) Rank[sa[i]]=i;    for(int i=0; i<n; h[Rank[i++]]=k)        for(k?k--:0,j=sa[Rank[i]-1]; r[i+k]==r[j+k]; k++) ;    return ;}int a[maxn*3];int sa[maxn*3],r[maxn*3];int s[maxn][2];int solve1(int len,int len1){    int n=len+len1+1;    long long sum=0,cont=0,top=0,tot=0;    for(int i=1;i<=n;i++)//维护a串    {        if(h[i]<kk) tot=0,top=0;//不能达到分组条件        else        {            cont=0;            if(sa[i-1]<len) cont++,tot+=h[i]-kk+1;//是a串的后缀            while(top>0&&h[i]<=s[top-1][0])//维护单调栈            {                top--;                tot-=s[top][1]*(s[top][0]-h[i]);//令小的值更新前面的,如果h[i]减小则前面提供给后面后面的b的后缀的子串也相应的减少                cont+=s[top][1];//累加前面的值用于统一更新            }            s[top][0]=h[i],s[top++][1]=cont;//a串后缀入栈,cont为1,b串后缀入队cont为0            if(sa[i]>len) sum+=tot;//b串的后缀,统计前面的结果        }    }    tot=0,top=0;    for(int i=1;i<=n;i++)//维护b串    {        if(h[i]<kk) tot=0,top=0;        else        {            cont=0;            if(sa[i-1]>len) cont++,tot+=h[i]-kk+1;            while(top>0&&h[i]<=s[top-1][0])            {                top--;                tot-=s[top][1]*(s[top][0]-h[i]);                cont+=s[top][1];            }            s[top][0]=h[i],s[top++][1]=cont;            if(sa[i]<len) sum+=tot;        }    }    printf("%lld\n",sum);}int main(){    while(~scanf("%d",&kk)&&kk)    {        string a[2];        cin>>a[0]>>a[1];        int len=a[0].size();        for(int i=0;i<len;i++)            x[i]=a[0][i];        x[len]=1;        int len1=a[1].size();        for(int i=0;i<len1;i++)            x[1+i+len]=a[1][i];        x[len+len1+1]=0;        nn=len+len1+1;        da(x,sa,nn+1,255);        get_height(x,sa,nn);        solve1(len,len1);    }}
0 0
原创粉丝点击