zoukankan      html  css  js  c++  java
  • 【训练记录】后缀数组

    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中则不需要,因为这两个本质上不是一个含义的

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

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

  • 相关阅读:
    CentOS 7,使用yum安装Nginx
    2019年6月Github最新开源java项目
    SQL Server清空数据库中ldf日志文件
    Spring Boot中使用 Thymeleaf
    Excel中使用Power Query获取网页json数据
    “工作做得越好,活越多,还不如偷懒?”这取决于你的目标
    Tomcat权威指南(第二版)下载pdf 高清完整中文版-百度云下载
    基于Xposed hook 实时监测微信消息
    Kotlin学习入门笔记
    批处理运行Vstest并生成HTML报告
  • 原文地址:https://www.cnblogs.com/DaD3zZ-Beyonder/p/5423695.html
Copyright © 2011-2022 走看看