【训练记录】后缀数组

POJ-1743

题目大意:给定一个字符串,求最长的重复的子串,且两个串不能重叠,且长度至少为5

思路:

题目中的重复,为同时加上减去同一个整数,仍算重复

转化成完全重复来判断,即每个字符为这个字符与它下一个字符的差值,这样可以拿后缀数组求解

判断是否有长度为k的子串是相同的,且不重叠,那么求最优,可以利用二分+判定

考虑利用height数组来求解,对于height值求解,对于排序后的Suffix分组,使每组的间的后缀值都不小于k

那么答案一定在同一组中,判断每组的SA最大和最小是否>=k,如果存在,即为有解,否则无解

CODE:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int read()
{
    int x=0,f=1; char ch=getchar();
    while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();}
    while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
#define maxn 40010
int wa[maxn],wb[maxn],wv[maxn],ws[maxn];
int S[maxn]; int SA[maxn]; int n;
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 p,*x=wa,*y=wb,*t;  
    for(int i=0; i<m; i++) ws[i]=0;  
    for(int i=0; i<n; i++) ws[x[i]=r[i]]++;
    for(int i=1; i<m; i++) ws[i]+=ws[i-1];  
    for(int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i;
    p=1;for(int j=1;p<n;j*=2,m=p)  
    {  
        p=0; for(int i=n-j;i<n;i++) y[p++]=i;
        for(int i=0; i<n; i++) if(sa[i]>=j) y[p++]=sa[i]-j;  
        for(int i=0; i<n; i++) wv[i]=x[y[i]];  
        for(int i=0; i<m; i++) ws[i]=0;  
        for(int i=0; i<n; i++) ws[wv[i]]++;  
        for(int i=1; i<m; i++) ws[i]+=ws[i-1];  
        for(int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i];
        t=x,x=y,y=t,p=1,x[sa[0]]=0;
        for(int i=1; i<n;i++)  
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;  
    }   
}  
int rank[maxn],height[maxn];  
void calheight(int *r,int *sa,int n)  
{  
    int i,j,k=0;  
    for(i=1;i<=n;i++) rank[sa[i]]=i;  
    for(i=0;i<n;height[rank[i++]]=k)
        for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++); 
    return;  
}
bool check(int x)
{
    int minn,maxx; 
    minn=maxx=SA[1];
    for (int i=2; i<=n; i++)
        {
            if (height[i]>=x && i<n)
                {
                    maxx=max(maxx,SA[i]);
                    minn=min(minn,SA[i]);
                    continue;
                }
            if (maxx-minn>=x) return 1;
            maxx=minn=SA[i];
        }
    return 0;
}
int main()
{
    while (1)
        {
            n=read(); if (!n) break;
            for (int i=0; i<n; i++) S[i]=read();
            for (int i=0; i<n; i++) S[i]=S[i+1]-S[i]+200;
            n--; S[n]=0;
            DA(S,SA,n+1,300);
            calheight(S,SA,n);
            int l=4,r=n,ans=0;
            while (l<=r)
                {
                    int mid=(l+r)>>1;
                    if (check(mid)) ans=mid,l=mid+1;
                    else r=mid-1;
                }
            ans++;
            if (ans<5) puts("0");
                else printf("%d
",ans);        
        }
    return 0;
}
POJ-1743

POJ-3621

题目大意:给定一个字符串,求k次最长重复的子串,这里串可以重叠

思路:

后缀数组 求 可重叠的k次最长重复子串

同上题的思想

先二分答案,然后对后缀分组

判断是否存在一个组,其后缀个数>=K,如果存在则存在解,否则不存在

时间复杂度O(nlogn)

CODE:

#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();}
    while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
#define maxn 20002 
int n,K;
int ws[maxn],wv[maxn],wa[maxn],wb[maxn];
int S[maxn],SA[maxn];
inline int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
inline void DA(int *r,int *sa,int n,int m)
{
    int p,*x=wa,*y=wb,*t;
    for (int i=0; i<m; i++) ws[i]=0;
    for (int i=0; i<n; i++) ws[x[i]=r[i]]++;
    for (int i=1; i<m; i++) ws[i]+=ws[i-1];
    for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i;
    p=1; for (int j=1; p<n; j*=2,m=p)
        {
            p=0; for (int i=n-j; i<n; i++) y[p++]=i;
            for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j;
            for (int i=0; i<n; i++) wv[i]=x[y[i]];
            for (int i=0; i<m; i++) ws[i]=0;
            for (int i=0; i<n; i++) ws[wv[i]]++;
            for (int i=1; i<m; i++) ws[i]+=ws[i-1];
            for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i];
            t=x,x=y,y=t;p=1;x[sa[0]]=0;
            for (int i=1; i<n; i++)
                x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        }
}
int rank[maxn],height[maxn];
inline void calheight(int *r,int *sa,int n)
{
    int k=0;
    for (int i=1; i<=n; i++) rank[sa[i]]=i;
    for (int i=0; i<n; height[rank[i++]]=k)
        {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);}
}
inline bool check(int x)
{
    int tmp=0,cnt=0;
    for (int i=1; i<=n; i++)
        {
            if (height[i]<x) 
                {if (cnt>tmp) tmp=cnt;cnt=0;}
            else 
                if (!cnt) cnt=2; else ++cnt;
        }
    if (cnt>tmp) tmp=cnt;
    if (tmp>=K) return 1;
        else return 0;
}
int main()
{
    n=read(),K=read();
    for (int i=0; i<n; i++) S[i]=read();
    S[n]=0;
    DA(S,SA,n+1,20001);
    calheight(S,SA,n);
    int l=1,r=n,mid;
    while (l<r)
        {
            mid=(l+r+1)>>1;
            if (check(mid)) l=mid;
                else r=mid-1;
        }
    printf("%d
",l);
    return 0;
}
POJ-3621

URAL-1297

题目大意:给定一个字符串,求出最长的回文串

思路:

后缀数组可以解决

枚举每一个字符,作为中间字符,这里分成回文串字符数为单数,和回文串字符数为双数两种来讨论

那么问题可以转化为:一个后缀和一个反串的后缀的最长公共前缀

做起来很简单,在原串后建反串,中间用一个特殊字符链接,这样问题就可以转化为 现在的串的两个后缀的最长公共前缀

那么求出Height数组后,变成RMQ问题,那么考虑采用ST的方法

CODE:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define maxn 1000100
char S[maxn]; int SA[maxn],len;
int wa[maxn],wb[maxn],ws[maxn],wv[maxn];
inline int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
inline void DA(char *r,int *sa,int n,int m)
{
    int p,*x=wa,*y=wb,*t;
    for (int i=0; i<m; i++) ws[i]=0;
    for (int i=0; i<n; i++) ws[x[i]=r[i]]++;
    for (int i=1; i<m; i++) ws[i]+=ws[i-1];
    for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i;
    p=1; for (int j=1; p<n; j*=2,m=p)
        {
            p=0; for (int i=n-j; i<n; i++) y[p++]=i;
            for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j;
            for (int i=0; i<n; i++) wv[i]=x[y[i]];
            for (int i=0; i<m; i++) ws[i]=0;
            for (int i=0; i<n; i++) ws[wv[i]]++;
            for (int i=1; i<m; i++) ws[i]+=ws[i-1];
            for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i];
            t=x,x=y,y=t;p=1;x[sa[0]]=0;
            for (int i=1; i<n; i++)
                x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        }
}
int rank[maxn],height[maxn];
inline void calheight(char *r,int *sa,int n)
{
    int k=0;
    for (int i=1; i<=n; i++) rank[sa[i]]=i;
    for (int i=0; i<n; height[rank[i++]]=k)
        {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);}
}
int log2[maxn]; int dp[maxn][21];
void ST(int n)
{
    log2[0]=-1;
    for (int i=1; i<=n; i++) 
        if (i&(i-1)) log2[i]=log2[i-1];
            else log2[i]=log2[i-1]+1;
    for (int i=1; i<=n; i++) dp[i][0]=height[i];
    for (int j=1; (1<<j)<=len; j++)
        for (int i=1; i+(1<<j)-1<=n; i++)
            dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
int RMQ(int l,int r)
{
    int tmp=log2[r-l+1];
    return min(dp[l][tmp],dp[r-(1<<tmp)+1][tmp]);
}
int LCP(int l,int r)
{
    l=rank[l]; r=rank[r]; 
    if (l>r) swap(l,r); l++;
    return RMQ(l,r);
}
int main()
{
    while (scanf("%s",S)!=EOF)
        { 
            len=strlen(S); int ll=len; S[len]=126;
            for (int i=0; i<len; i++) S[len+i+1]=S[len-i-1];
            len=len*2+1; S[len]=0;
            DA(S,SA,len+1,200); calheight(S,SA,len);
            ST(len); int st=0,ans=1;
            for (int i=0; i<ll; i++)
                {
                    int odd=LCP(i,len-i-1);//奇数的情况
                    if ((odd<<1)-1>ans) ans=(odd<<1)-1,st=i-odd+1;
                    int even=LCP(i,len-i);//偶数的情况
                    if ((even<<1)>ans) ans=even<<1,st=i-even;
                }
            if (ans==1) putchar(S[0]);
            else for (int i=st; i<st+ans; i++) putchar(S[i]);
            puts("");
        }
    return 0;
}
URAL-1297

POJ-2406

题目大意:给定字符串,求最大重复次数

思路:

KMP很好想,暴力也很好想,都可A

后缀数组的方法,求出Height后,枚举k

先判断总串长能否整除,再判断Suffix(1)和Suffix(k+1)的LCP是否为n-k;

PS:倍增的后缀数组会被卡,只能用DC3..MDZZ!!!

CODE:

自己TLE的:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 1001000
char S[maxn];int SA[maxn],len;
int wa[maxn],wb[maxn],ws[maxn],wv[maxn];
inline int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
inline void DA(char *r,int *sa,int n,int m)
{
    int p,*x=wa,*y=wb,*t;
    for (int i=0; i<m; i++) ws[i]=0;
    for (int i=0; i<n; i++) ws[x[i]=r[i]]++;
    for (int i=1; i<m; i++) ws[i]+=ws[i-1];
    for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i;
    p=1; for (int j=1; p<n; j*=2,m=p)
        {
            p=0; for (int i=n-j; i<n; i++) y[p++]=i;
            for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j;
            for (int i=0; i<n; i++) wv[i]=x[y[i]];
            for (int i=0; i<m; i++) ws[i]=0;
            for (int i=0; i<n; i++) ws[wv[i]]++;
            for (int i=1; i<m; i++) ws[i]+=ws[i-1];
            for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i];
            t=x,x=y,y=t;p=1;x[sa[0]]=0;
            for (int i=1; i<n; i++)
                x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        }
}
int rank[maxn],height[maxn];
inline void calheight(char *r,int *sa,int n)
{
    int k=0;
    for (int i=1; i<=n; i++) rank[sa[i]]=i;
    for (int i=0; i<n; height[rank[i++]]=k)
        {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);}
}
int minn[maxn];
void prework()
{
    int mid=rank[0]; minn[mid]=maxn;
    for (int i=mid-1; i>=0; i--) 
        if (minn[i+1]>height[i+1]) minn[i]=height[i+1];
            else minn[i]=minn[i+1];
    for (int i=mid+1; i<=len; i++)
        if (minn[i-1]>height[i]) minn[i]=height[i];
            else minn[i]=minn[i-1];
}
int main()
{
    while (scanf("%s",S)!=EOF&&(S[0]!='.'&&strlen(S)!=1))
        {
            len=strlen(S); S[len]=0;
            DA(S,SA,len+1,128); calheight(S,SA,len);
            prework();
            int ans=1;
            for (int i=1; i<=len/2; i++)
                {
                    if (len%i) continue;
                    if (minn[rank[i]]==len-i) {ans=len/i;break;}
                }
            printf("%d
",ans);
        }
    return 0;
}
POJ-2406

别人DC3 AC版:

#include <stdio.h>
#include<string.h>
#define maxn 1000001

char c;
int r[maxn*3],sa[maxn*3];
int ans[maxn];
char str[maxn*3];



#define F(x) ((x)/3+((x)%3==1?0:tb))
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
int wa[maxn],wb[maxn],wv[maxn],ws[maxn];
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++) ws[i]=0;
     for(i=0;i<n;i++) ws[wv[i]]++;
     for(i=1;i<m;i++) ws[i]+=ws[i-1];
     for(i=n-1;i>=0;i--) b[--ws[wv[i]]]=a[i];
     return;
}
void dc3(int *r,int *sa,int n,int m)      // r为待匹配数组  n为总长度 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<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 rank[maxn],height[maxn];
void calheight(int *r,int *sa,int n) //  求height数组。
{
     int i,j,k=0;
     for(i=1;i<=n;i++) rank[sa[i]]=i;
     for(i=0;i<n;height[rank[i++]]=k)
     for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
     return;
}
int RMQ[maxn];
int mm[maxn];

///int best[20][maxn];//best[i][j] 表示从j开始的长度为2的i次方的一段元素的最小值
/*void initRMQ(int n)///O(Nlogn) 预处理
{
     int i,j,a,b;
     for(mm[0]=-1,i=1;i<=n;i++)
     mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
     for(i=1;i<=n;i++) best[0][i]=i;
     for(i=1;i<=mm[n];i++)
     for(j=1;j<=n+1-(1<<i);j++)
     {
       a=best[i-1][j];
       b=best[i-1][j+(1<<(i-1))];
       if(RMQ[a]<RMQ[b]) best[i][j]=a;
       else best[i][j]=b;
     }
     return;
}
int askRMQ(int a,int b)///询问a,b后缀的最长公共前缀  O(1)查询
{
    int t;
    t=mm[b-a+1];b-=(1<<t)-1;
    a=best[t][a];b=best[t][b];
    return RMQ[a]<RMQ[b]?a:b;
}
int lcp(int a,int b)
{
    int t;
    a=rank[a];b=rank[b];
    if(a>b) {t=a;a=b;b=t;}
    return(height[askRMQ(a+1,b)]);
}
*/
int f[maxn];//f[i]表示lcp(0,i);
void get_f(int n)
{
   int i,j,mmin;
   j=rank[0];
   mmin=999999999;
   /*以下2个循环内的代码顺序不同的原因是 i和j的最长公共前缀lcp(rank[i],rank[j])的值应为
   rmq(height,rank[i]+1,rank[j])  注意有个+1
   */
   for(i=j;i>=1;i--)
   {
       f[i]=mmin;
       mmin=mmin<height[i]?mmin:height[i];//应该包括height[j]

   }
   mmin=999999999;
   for(i=j+1;i<=n;i++)
   {
       mmin=mmin<height[i]?mmin:height[i]; //不应该包括height[j]
       f[i]=mmin;
   }

}

int main()
{
    int i,n;
      while(scanf("%s",str)!=EOF)
     {
             n=strlen(str);
             if(n==1&&str[0]=='.') break;
             for(i=0;i<n;i++)  r[i]=str[i]-'a'+1;
             r[n]=0;
             dc3(r,sa,n+1,123);//千万注意+1
             calheight(r,sa,n);
            // initRMQ(n);
        /*
        for(i=0; i<n+1; i++)  // rank[i] : suffix(i)排第几
           printf("rank[%d] =  %d
",i,rank[i]);
        printf("
");
        for(i=0; i<n+1; i++)   // sa[i] : 排在第i个的是谁
           printf("sa[%d] =  %d
",i,sa[i]);
         */
            int  len;
            int  mmax=0;
            get_f(n);
             for(len=1;len<=n;len++)
             {
                   if(n%len==0)
                   {
                       if(f[rank[len]]==(n-len))
       ///注意是rank[len],因为这里在求0和0+len的lcp ,即要求rank[0]到rank[len]之间的最小height值
                       {
                            mmax=n/len;
                            break;
                       }
                   }

             }
             if(mmax!=0)
             printf("%d
",mmax);
             else printf("1
");
     }
    return 0;
}
POJ-2406

POJ-3693

题目大意:给定一个字符串,求重复次数最多的连续重复子串

思路:好题!

首先想到枚举长度L,求出长度为L的连续子串最多能连续出现几次;

假设在原字符串中连续出现2次,记这个子字符串为S,那么S肯定包括了字符r[0],r[L],r[L*2],r[L*3],……中的某相邻的两个。所以只须看字符r[L*i]和r[L*(i+1)]往前和往后各能匹配到多远,记这个总长度为K,那么这里连续出现了K/L+1次。

设LCP长度为M, 则答案显然为M / L + 1, 但这不一定是最好的, 因为答案的首尾不一定再我们枚举的位置上. 我的解决方法是, 我们考虑M % L的值的意义, 我们可以认为是后面多了M % L个字符, 但是我们更可以想成前面少了(L - M % L)个字符! 所以我们求后缀j * L - (L - M % L)与后缀(j + 1) * L - (L - M % L)的最长公共前缀。

即把之前的区间前缀L-M%L即可。

然后把可能取到最大值的长度L保存,由于 题目要求字典序最小,通过sa数组进行枚举,取到的第一组,肯定是字典序最小的。

CODE:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 100010
char S[maxn]; int SA[maxn];
int wa[maxn],wb[maxn],ws[maxn],wv[maxn];
inline int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
inline void DA(char *r,int *sa,int n,int m)
{
    int p,*x=wa,*y=wb,*t;
    for (int i=0; i<m; i++) ws[i]=0;
    for (int i=0; i<n; i++) ws[x[i]=r[i]]++;
    for (int i=1; i<m; i++) ws[i]+=ws[i-1];
    for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i;
    p=1; for (int j=1; p<n; j*=2,m=p)
        {
            p=0; for (int i=n-j; i<n; i++) y[p++]=i;
            for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j;
            for (int i=0; i<n; i++) wv[i]=x[y[i]];
            for (int i=0; i<m; i++) ws[i]=0;
            for (int i=0; i<n; i++) ws[wv[i]]++;
            for (int i=1; i<m; i++) ws[i]+=ws[i-1];
            for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i];
            t=x,x=y,y=t;p=1;x[sa[0]]=0;
            for (int i=1; i<n; i++)
                x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        }
}
int rank[maxn],height[maxn];
inline void calheight(char *r,int *sa,int n)
{
    int k=0;
    for (int i=1; i<=n; i++) rank[sa[i]]=i;
    for (int i=0; i<n; height[rank[i++]]=k)
        {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);}
}
int log2[maxn]; int dp[maxn][21];
void ST(int n)
{
    log2[0]=-1;
    for (int i=1; i<=n; i++) 
        if (i&(i-1)) log2[i]=log2[i-1];
            else log2[i]=log2[i-1]+1;
    for (int i=1; i<=n; i++) dp[i][0]=height[i];
    for (int j=1; (1<<j)<=n; j++)
        for (int i=1; i+(1<<j)-1<=n; i++)
            dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
int RMQ(int l,int r)
{
    int tmp=log2[r-l+1];
    return min(dp[l][tmp],dp[r-(1<<tmp)+1][tmp]);
}
int LCP(int l,int r)
{
    l=rank[l]; r=rank[r]; 
    if (l>r) swap(l,r); l++;
    return RMQ(l,r);
}
int main()
{
    int t=1;
    while (scanf("%s",S)&&(S[0]!='#'&&strlen(S)!=1))
        {
            int len=strlen(S); S[len]=0;
            DA(S,SA,len+1,128); calheight(S,SA,len); ST(len);
            int cnt=0,maxx=0,a[maxn];
            for (int L=1; L<len; L++)
                for (int i=0; i<len-L; i+=L)
                    {
                        int r=LCP(i,i+L),st=r/L+1,k=i-(L-r%L);
                        if (k>=0&&r%L)
                            if (LCP(k,k+L)>=r) st++;
                        if (st>maxx) maxx=st,cnt=0,a[cnt++]=L;
                            else if (st==maxx) a[cnt++]=L;                            
                    }
            int le=-1,sta;
            for (int i=1; i<=len&&le==-1; i++)
                {
                    for (int j=0; j<cnt; j++)
                        {
                            int l=a[j];
                            if (LCP(SA[i],SA[i]+l)>=(maxx-1)*l)
                                {le=l;sta=SA[i];break;}
                        }
                }
            printf("Case %d: ",t++);
            for (int i=sta,j=0; j<le*maxx;j++,i++) putchar(S[i]);
            puts("");
        }
    return 0;
}
POJ-3693

POJ-2774 && URAL-1517

题目大意:给定两个字符串,求两个串最长公共子串,输出方案

思路:

相同的两道题,POJ-2774结果为长度,URAL-1517结果为方案..

考虑后缀数组,把第二个串扔到第一个串后,中间以特殊字符链接

求出height数组,根据SA找公共子串,更新最大值即可

这里需要注意,当Suffix(SA[i-1])和Suffix(SA[i])不在同一字符串时,height[i]才满足题意,才能用来更新ans,顺便记录起始位置输出即可

CODE:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 200010
char S[maxn]; int SA[maxn];
int wa[maxn],wb[maxn],ws[maxn],wv[maxn];
inline int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
inline void DA(char *r,int *sa,int n,int m)
{
    int p,*x=wa,*y=wb,*t;
    for (int i=0; i<m; i++) ws[i]=0;
    for (int i=0; i<n; i++) ws[x[i]=r[i]]++;
    for (int i=1; i<m; i++) ws[i]+=ws[i-1];
    for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i;
    p=1; for (int j=1; p<n; j*=2,m=p)
        {
            p=0; for (int i=n-j; i<n; i++) y[p++]=i;
            for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j;
            for (int i=0; i<n; i++) wv[i]=x[y[i]];
            for (int i=0; i<m; i++) ws[i]=0;
            for (int i=0; i<n; i++) ws[wv[i]]++;
            for (int i=1; i<m; i++) ws[i]+=ws[i-1];
            for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i];
            t=x,x=y,y=t;p=1;x[sa[0]]=0;
            for (int i=1; i<n; i++)
                x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        }
}
int rank[maxn],height[maxn];
inline void calheight(char *r,int *sa,int n)
{
    int k=0;
    for (int i=1; i<=n; i++) rank[sa[i]]=i;
    for (int i=0; i<n; height[rank[i++]]=k)
        {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);}
}
int main()
{
    int ans=0,len,len1;
    scanf("%s",S); len1=len=strlen(S); S[len]='@';
    scanf("%s",S+len+1); len=strlen(S); S[len]='0';
    DA(S,SA,len+1,200); calheight(S,SA,len);
    for (int i=1; i<=len; i++)
        if ((SA[i]<len1&&SA[i-1]>len1) || (SA[i-1]<len1&&SA[i]>len1))
            ans=max(ans,height[i]);
    printf("%d
",ans);
    return 0;
}
POJ-2774
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 200010
char S[maxn]; int SA[maxn];
int wa[maxn],wb[maxn],ws[maxn],wv[maxn];
inline int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
inline void DA(char *r,int *sa,int n,int m)
{
    int p,*x=wa,*y=wb,*t;
    for (int i=0; i<m; i++) ws[i]=0;
    for (int i=0; i<n; i++) ws[x[i]=r[i]]++;
    for (int i=1; i<m; i++) ws[i]+=ws[i-1];
    for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i;
    p=1; for (int j=1; p<n; j*=2,m=p)
        {
            p=0; for (int i=n-j; i<n; i++) y[p++]=i;
            for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j;
            for (int i=0; i<n; i++) wv[i]=x[y[i]];
            for (int i=0; i<m; i++) ws[i]=0;
            for (int i=0; i<n; i++) ws[wv[i]]++;
            for (int i=1; i<m; i++) ws[i]+=ws[i-1];
            for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i];
            t=x,x=y,y=t;p=1;x[sa[0]]=0;
            for (int i=1; i<n; i++)
                x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        }
}
int rank[maxn],height[maxn];
inline void calheight(char *r,int *sa,int n)
{
    int k=0;
    for (int i=1; i<=n; i++) rank[sa[i]]=i;
    for (int i=0; i<n; height[rank[i++]]=k)
        {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);}
}
int main()
{
    int ans=0,len,len1; int tt,st; scanf("%d",&tt);
    scanf("%s",S); len1=len=strlen(S); S[len]='@';
    scanf("%s",S+len+1); len=strlen(S); S[len]='0';
    DA(S,SA,len+1,200); calheight(S,SA,len);
    for (int i=1; i<=len; i++)
        if ((SA[i]<len1&&SA[i-1]>len1) || (SA[i-1]<len1&&SA[i]>len1))
            if (ans<height[i]) ans=height[i],st=SA[i-1];
    for (int i=st; i<st+ans; i++) putchar(S[i]); puts("");
//    printf("%d
",ans);
    return 0;
}
URAL-1517

POJ-3294

题目大意:给定T个串,求存在于大于T个串的最长公共子串

思路:

后缀数组一样可以解决,思路也有之前的类似

把T个串全都衔接在一起,每两个串直接用不同的特殊字符链接,求出height数组后,进行分组

二分,判断每组中的后缀是否存在于不小于T/2个串中,记录答案的首位值即可;

PS:这题不能乱memset,memset多了是会TLE的,理性区别数组的大小,和memset的多少(蟹蟹CA学长)

CODE:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 200010
int S[maxn];int SA[maxn],minl,T;
int wa[maxn],wb[maxn],ws[maxn],wv[maxn];
inline int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
inline void DA(int *r,int *sa,int n,int m)
{
    int p,*x=wa,*y=wb,*t;
    for (int i=0; i<m; i++) ws[i]=0;
    for (int i=0; i<n; i++) ws[x[i]=r[i]]++;
    for (int i=1; i<m; i++) ws[i]+=ws[i-1];
    for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i;
    p=1; for (int j=1; p<n; j*=2,m=p)
        {
            p=0; for (int i=n-j; i<n; i++) y[p++]=i;
            for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j;
            for (int i=0; i<n; i++) wv[i]=x[y[i]];
            for (int i=0; i<m; i++) ws[i]=0;
            for (int i=0; i<n; i++) ws[wv[i]]++;
            for (int i=1; i<m; i++) ws[i]+=ws[i-1];
            for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i];
            t=x,x=y,y=t;p=1;x[sa[0]]=0;
            for (int i=1; i<n; i++)
                x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        }
}
int rank[maxn],height[maxn];
inline void calheight(int *r,int *sa,int n)
{
    int k=0;
    for (int i=1; i<=n; i++) rank[sa[i]]=i;
    for (int i=0; i<n; height[rank[i++]]=k)
        {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);}
}
bool visit[1010];int pos[maxn];
bool check(int x,int n)
{
    int cnt=0;
    memset(visit,0,sizeof(visit));
    for (int i=2; i<=n; i++)
        {
            if (height[i]<x)
                {memset(visit,0,sizeof(visit));cnt=0;continue;}
            if (!visit[pos[SA[i-1]]]) visit[pos[SA[i-1]]]=1,cnt++;
            if (!visit[pos[SA[i]]]) visit[pos[SA[i]]]=1,cnt++;
            if (cnt>T/2) return 1;
        }
    return 0;
}
void output(int x,int n)
{
    int cnt=0,mark=0;
    memset(visit,0,sizeof(visit));
    for (int i=2; i<=n; i++)
        {
            if (height[i]<x) 
                {memset(visit,0,sizeof(visit)); cnt=0,mark=0; continue;}
            if (!visit[pos[SA[i-1]]]) visit[pos[SA[i-1]]]=1,cnt++;
            if (!visit[pos[SA[i]]]) visit[pos[SA[i]]]=1,cnt++;
            if (cnt>T/2&&!mark)
                {
                    for (int j=0; j<x; j++)
                        putchar(S[SA[i]+j]+'a'-1);
                    puts("");
                    mark=1;
                }
        } 
}        
int main()
{
    while (scanf("%d",&T)!=EOF&&T)
        {
            int len=0,ch=28,ans=0; minl=maxn;
            for (int i=0; i<T; i++)
                {
                    char s[1100]; scanf("%s",s);
                    if (strlen(s)<minl) minl=strlen(s);
                    for (int j=0; s[j]; j++)
                        S[len]=s[j]-'a'+1,pos[len]=i,len++;
                    S[len]=ch; pos[len]=ch; ch++; len++;
                }
            S[len]=0;
            DA(S,SA,len+1,ch); calheight(S,SA,len);
            int l=0,r=minl;
            while (l<=r)
                {
                    int mid=(l+r)>>1;
                    if (check(mid,len)) l=mid+1,ans=mid;
                        else r=mid-1;
                }
            if (!ans) puts("?"),puts("");
                else output(ans,len),puts("");
        }
    return 0;
}
POJ-3294

POJ-3415

题目大意:给出两个串,求大于K的公共子串个数(允许重复)

思路:好题!

基本思路是计算A的所有后缀和B的所有后缀之间的最长公共前缀的长度,把最长公共前缀长度不小于k的部分全部加起来。

先将两个字符串连起来,中间用一个没有出现过的字符隔开。按height值分组后,接下来的工作便是快速的统计每组中后缀之间的最长公共前缀之和。

扫描一遍,每遇到一个B的后缀就统计与前面的A的后缀能产生多少个长度不小于k的公共子串,这里A的后缀需要用一个单调的栈来高效的维护。

然后对A也这样做一次。

有一些细节:

用一个单调栈,栈中存放两个元素分别height_top与cnt_top,分别表示到i为止的最小height和A串的数目。维护栈中元素的height从顶到底递减:每加入一个元素如果该元素比栈顶元素小则需要将tot中cnt_top个已经累计的height_top全部替换为当前元素的height(lcp是取区间最小值)。

CODE:

#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
#define maxn 1001000
char S[maxn];int SA[maxn],len;
int wa[maxn],wb[maxn],ws[maxn],wv[maxn];
inline int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
inline void DA(char *r,int *sa,int n,int m)
{
    int p,*x=wa,*y=wb,*t;
    for (int i=0; i<m; i++) ws[i]=0;
    for (int i=0; i<n; i++) ws[x[i]=r[i]]++;
    for (int i=1; i<m; i++) ws[i]+=ws[i-1];
    for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i;
    p=1; for (int j=1; p<n; j*=2,m=p)
        {
            p=0; for (int i=n-j; i<n; i++) y[p++]=i;
            for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j;
            for (int i=0; i<n; i++) wv[i]=x[y[i]];
            for (int i=0; i<m; i++) ws[i]=0;
            for (int i=0; i<n; i++) ws[wv[i]]++;
            for (int i=1; i<m; i++) ws[i]+=ws[i-1];
            for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i];
            t=x,x=y,y=t;p=1;x[sa[0]]=0;
            for (int i=1; i<n; i++)
                x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        }
}
int rank[maxn],height[maxn];
inline void calheight(char *r,int *sa,int n)
{
    int k=0;
    for (int i=1; i<=n; i++) rank[sa[i]]=i;
    for (int i=0; i<n; height[rank[i++]]=k)
        {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);}
}
int stack[maxn][2];
int main()
{
    int K;
    while (scanf("%d",&K)!=EOF && K)
        {
            scanf("%s",S); int l1=len=strlen(S); S[len]='@';
            scanf("%s",S+len+1); int l2=strlen(S+len+1); len=strlen(S); S[len]=0;
            DA(S,SA,len+1,200); calheight(S,SA,len);
            int top=0,cnt=0;long long ans=0,tot=0;
            for(int i=1;i<=len;i++) 
                {
                    if(height[i]<K) top=0,tot=0;
                        else 
                            {
                                cnt=0;
                                if(SA[i-1]<l1) cnt++,tot+=height[i]-K+1;
                            }
                       while(top && height[i]<=stack[top-1][0]) 
                        {
                            top--;
                            tot+=(height[i]-stack[top][0])*stack[top][1];
                            cnt+=stack[top][1];
                        }
                    stack[top][0]=height[i],stack[top++][1]=cnt;
                    if(SA[i]>l1) ans+=tot;
                }    
            top=tot=cnt=0;
            for(int i=1;i<=len;i++)
                {
                    if(height[i]<K) top=0,tot=0;
                        else 
                            {
                                cnt=0;
                                if(SA[i-1]>l1) 
                                    cnt++,tot+=height[i]-K+1;
                            }
                    while(top && height[i]<=stack[top-1][0]) 
                        {
                            top--;
                            tot+=(height[i]-stack[top][0])*stack[top][1];
                            cnt+=stack[top][1];
                        }
                    stack[top][0]=height[i],stack[top++][1]=cnt;
                    if(SA[i]<l1) ans+=tot;
                }
            printf("%lld
",ans);
        }
    return 0;
}
POJ-3415
THE END.

一点点感想:

有一些惯用的套路和基本的思路:

有一些问题,可以考虑对原串建反串,利用反串的后缀数组来求解

多串的问题,可以将串链接起来,求后缀数组后,利用各串间的关系求解

有两种比较容易见到的方法:

对height数组分组,二分对每个组进行一些统计;

与RMQ问题相结合,去进行LCP有关的处理;

一些可能需要注意的东西吧:

DA里的m,表示的是基数排序的限制,如果是纯字母,一般可以用128限制,纯数字,就用最大数字限制好了

字符串的处理,可以提前转成数,可能会方便一些(某次用字符狂RE,只是化成数就A了)

处理好len的长度问题,DA中len要多1位,calheight中则不需要,因为这两个本质上不是一个含义的

多串相连接,要注意串与串之间的字符不要重复了,否则可能出现问题

在做题的过程中,应该从后缀间的关系进行入手,模板几乎是不变的,其实重点还是在于对后缀数组一些性质和方法的理解吧

原文地址:https://www.cnblogs.com/DaD3zZ-Beyonder/p/5423695.html