后缀数组 POJ 3693 && hdu 2459 Maximum repetition substring

来源:互联网 发布:娇喘合集 网络歌手 编辑:程序博客网 时间:2024/05/21 10:20

Maximum repetition substring


Problem Description
The repetition number of a string is defined as the maximum number R such that the string can be partitioned into R same consecutive substrings. For example, the repetition number of "ababab" is 3 and "ababa" is 1.

Given a string containing lowercase letters, you are to find a substring of it with maximum repetition number.
 

Input
The input consists of multiple test cases. Each test case contains exactly one line, which
gives a non-empty string consisting of lowercase letters. The length of the string will not be greater than 100,000.

The last test case is followed by a line containing a '#'.
 

Output
For each test case, print a line containing the test case number( beginning with 1) followed by the substring of maximum repetition number. If there are multiple substrings of maximum repetition number, print the lexicographically smallest one.
 

Sample Input
ccabababcdaabbccaa#
 

Sample Output
Case 1: abababCase 2: aa
 

论文上原话是这么说的:

算法分析:

先穷举长度 L,然后求长度为 L 的子串最多能连续出现几次。首先连续出现
1 次是肯定可以的,所以这里只考虑至少 2 次的情况。假设在原字符串中连续出
现 2 次,记这个子字符串为 S,那么 S 肯定包括了字符 r[0], r[L], r[L*2],
r[L*3], ……中的某相邻的两个。所以只须看字符 r[L*i]和 r[L*(i+1)]往前和
往后各能匹配到多远,记这个总长度为 K,那么这里连续出现了 K/L+1 次。最后
看最大值是多少。如图 7 所示。
穷举长度 L 的时间是 n,每次计算的时间是 n/L。所以整个做法的时间复杂

度是 O(n/1+n/2+n/3+……+n/n)=O(nlogn)。


具体实现的时候

在字符串A上进行枚举, 长度作为循环单位,关键代码

for (int l = 1;l < len; ++l ){        int m , k , t;        //枚举每一个区间A[0],A[0+l*1],A[0+L*2]......        for ( int j = 0;j < len; j += l){            //记录最长相同的长度            k = query( j , j+l );            //向前寻找更大匹配,            m = k/l + 1;            t = j - ( l - k%l );            if ( t >= 0 && k%l )                if ( query( t,t+l ) > k ) m++;            //更新最大长度            if ( m > maxTimes ){                maxTimes = m;                lenIdx = 0;                ans_len[lenIdx++] = l;            }        }    }
   
           拿c   c  a  b  a  b  a  b  c  #(0) 来说,

          0  1  2  3  4  5  6  7  8  9 

SA 数组  9  2  4  6  3  5  7  8  1  0

rank        9  8  1  4  2  5  3  6  7  0

height     0  0  4  2  0  3  1  0  1  1

在下标处循环,len == 1时 查询 区间(0,1) ( 1,2) (2,3)。。

         len == 2时 查询 区间(0,2) ( 2,4) (4,6)。。

   。。。。(查询区间(0,1)是指,从0开始的字串,跟从1开始的字串,最大重复数)

对于这个串来说显然,l == 2 时,  区间2 (abababc)跟区间4 (ababc)最大重复串 k == 4,所以   k/ l + 1 == 3 ,所以重复最大是3,记录长度3

此处有个关系, 

对于一个字符串如果他是由R个长度为L的字符串重复形成的,那么必然有两个连续字串 str( i*l ),str((i+1)*l) 的最大重复长度等于

 L*(R-1)


附上代码

#include <iostream>#include <cstdio>#include <climits>#include <cmath>#include <cstring>using namespace std;int const SIZE = 200050;//辅助数组,以下划线开头int _wa[SIZE],_wb[SIZE],_wv[SIZE],_ws[SIZE];//辅助函数int _cmp(int const r[],int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}//求后缀数组的倍增算法//r: 源数组,且除r[n-1]外,其余r[i]>0//n: r的长度//m: r中的元素取值的上界,即任意r[i]<m//sa:后缀数组,即结果void da(int const r[],int n,int m,int sa[]){    int i,j,p,*x=_wa,*y=_wb,*t;    for(i=0;i<m;i++) _ws[i] = 0;    for(i=0;i<n;i++) _ws[x[i] = r[i]]++;    for(i=1;i<m;i++) _ws[i] += _ws[i-1];    for(i=n-1;i>=0;i--) sa[--_ws[x[i]]]=i;    for(j=1,p=1;p<n;j*=2,m=p){        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;        for(i=0;i<n;i++) _wv[i]=x[y[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--) sa[--_ws[_wv[i]]] = y[i];        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)            x[sa[i]]=_cmp(y,sa[i-1],sa[i],j)?p-1:p++;    }    return;}//计算rank数组与height数组//r:  源数组//sa: 后缀数组//n:  源数组的长度//rank: rank数组,即计算结果//height: height数组,即计算结果void calHeight(int const r[],int const sa[],int n,int rank[],int height[]){    int i,j,k=0;    for(i=1;i<n;i++) rank[sa[i]]=i;    for(i=0;i<n-1;height[rank[i++]]=k)    for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);    return;}char A[SIZE];int R[SIZE],SA[SIZE];int Rank[SIZE],Height[SIZE];int Min[SIZE][20];void init(int n){    int i,j;    for(i=0;i<n;i++)Min[i][0] = *(Height+i);    for(j=1;(1<<j)<=n;j++){        for(i=0;i+(1<<j)<=n;i++){            int x = Min[i][j-1];            int y = Min[i+(1<<(j-1))][j-1];            if ( x <= y ) Min[i][j] = x;            else          Min[i][j] = y;        }    }    return ;}//查询区间最小值,此处查询a+1到b的int query(int a,int b){    //先对应字符串区间到height区间    a = Rank[a] , b = Rank[b];    //height数组上前开后闭    if ( a > b ) swap(a,b);    a++;    int k  = (int)( log((double)(b-a+1)) / log(2.0) );    int minak = Min[a][k];    int minkb = Min[b+1-(1<<k)][k];    if ( minak > minkb ) minak = minkb;    return minak;}int maxTimes = 0; // 最大重复次数int ans_len[SIZE];// 最大重复次数的长度int lenIdx = 0;   //ans_len 的长度void Solve( int len ){    maxTimes = lenIdx = 0;    //枚举长度,从1开始    for (int l = 1;l < len; ++l ){        int m , k , t;        //枚举每一个区间A[0],A[0+l*1],A[0+L*2]......        for ( int j = 0;j < len; j += l){            //记录最长相同的长度            k = query( j , j+l );            //向前寻找更大匹配,            m = k/l + 1;            t = j - ( l - k%l );            if ( t >= 0 && k%l )                if ( query( t,t+l ) > k ) m++;            //更新最大长度            if ( m > maxTimes ){                maxTimes = m;                lenIdx = 0;                ans_len[lenIdx++] = l;            }        }    }}//记录起点int start;void MakeAns( int len ){    //从SA[1]开始枚举,找到第一个符合重复次数的位置    bool flag = 0;    for (int i = 1;i < len;++i ){        if ( flag ) break;        for (int k = 0;k < lenIdx;++k ){            int l = ans_len[k];            //判断是否符合重复次数            if ( query(SA[i], SA[i] + l ) >= (maxTimes-1) * l ){                start = SA[i];                //处理一下结束位置,方便输出                A[ start + l*maxTimes ] = '\0';                flag = 1;                break;            }        }    }}int main(){    int n,kase = 1;    while( scanf("%s", A ) != EOF ){        if ( *A == '#' ) return 0;        n = strlen(A);        for (int i = 0;i < n;++i) R[i] = A[i] - 'a' + 1;        R[n] = 0;        da(R,n+1,30,SA);        calHeight(R,SA,n+1,Rank,Height);        dispa        init( n+1 ); //ST预处理        Solve( n+1 );//计算最大重复次数及对应的长度        MakeAns(n+1);//根据重复次数构造字串        printf("Case %d: %s\n", kase++, &A[start]);    }    return 0;}


如果只求最大重复次数 下面模板会快一点


#include<cstdio>  #include<cstdlib>  #include<cstring>  #include<cmath>  #include<vector>  #include<algorithm>  using namespace std;  const int N = 100000+50;  int cmp(int *r,int a,int b,int l)  {      return (r[a]==r[b]) && (r[a+l]==r[b+l]);  }  // 用于比较第一关键字与第二关键字,  // 比较特殊的地方是,预处理的时候,r[n]=0(小于前面出现过的字符)  /*     DA(aa,sa,n+1,200);     calheight(aa,sa,n); */  int wa[N],wb[N],ws[N],wv[N];  int Rank[N];//后缀i在sa[]中的排名  int height[N];//sa[i]与sa[i-1]的LCP  int sa[N];//sa[i]表示排名第i小的后缀的下标  void DA(int *r,int *sa,int n,int m)  //此处N比输入的N要多1,为人工添加的一个字符,用于避免CMP时越界  {      int i,j,p,*x=wa,*y=wb,*t;      for(i=0; i<m; i++) ws[i]=0;      for(i=0; i<n; i++) ws[x[i]=r[i]]++;      for(i=1; i<m; i++) ws[i]+=ws[i-1];      for(i=n-1; i>=0; i--) sa[--ws[x[i]]]=i; //预处理长度为1      for(j=1,p=1; p<n; j*=2,m=p) //通过已经求出的长度J的SA,来求2*J的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; //利用长度J的,按第二关键字排序          for(i=0; i<n; i++) wv[i]=x[y[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--) sa[--ws[wv[i]]]=y[i]; //基数排序部分          for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1; i<n; i++)              x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;  //更新名次数组x[],注意判定相同的      }  }    void calheight(int *r,int *sa,int n)  // 此处N为实际长度  {      int i,j,k=0;        // height[]的合法范围为 1-N, 其中0是结尾加入的字符      for(i=1; i<=n; i++) Rank[sa[i]]=i; // 根据SA求Rank      for(i=0; i<n; height[Rank[i++]] = k ) // 定义:h[i] = height[ Rank[i] ]          for(k?k--:0,j=sa[Rank[i]-1]; r[i+k]==r[j+k]; k++); //根据 h[i] >= h[i-1]-1 来优化计算height过程  }  int n;  char ss[N];  int aa[N];  const int maxn=100000+50;  int mn[100000][20];  int log22[100000+50];  void pre()  {      for (int i=1; i<=n; i++)          log22[i]=log2(i);  }  void rmq_init(int n,int *h)  {      for (int j=1; j<=n; j++) mn[j][0]=h[j];      int m=log22[n];      for (int i=1; i<=m; i++)          for (int j=n; j>0; j--)          {              mn[j][i]=mn[j][i-1];              if ( j+(1<<(i-1)) <=n ) mn[j][i]=min(mn[j][i], mn[j+(1<<(i-1)) ] [i-1]);          }  }  int lcp_min(int l,int r) //求lcp(l,r)  {      if (l>r)swap(l,r);  //先交换      l++;                //根据height定义,l++      int m=log22[r-l+1];      return min(mn[l][m],mn[r-(1<<m)+1][m]);  }    int solve()  {      int ans=1;      for (int  L=1; L<=n; L++)      {          for (int j=0; j<n; j+=L)          {              int lcp_len=lcp_min( Rank[j],Rank[j+L]);              ans=max(ans,lcp_len/L+1);              int last_possible_pos=j-(L-lcp_len%L);              if (last_possible_pos>=0)                  ans=max(ans, 1+lcp_min( Rank[last_possible_pos] ,Rank[last_possible_pos+L]   )/L  ) ;          }      }      return ans;  }    int main ()  {   //    printf("%d ",'z'-'a');      scanf("%s",&ss);      n=strlen(ss);      for (int i=0; i<n; i++)           aa[i]=ss[i]-'a'+1;       aa[n]=0;      DA(aa,sa,n+1,30);      calheight(aa,sa,n);      pre();      rmq_init(n,height);      //---------------------------------------------pre      int ans= solve();      printf("%d\n",ans);      return 0;  }  





0 0
原创粉丝点击