题意 : 给定一个字符串,求重复次数最多的连续重复子串。POJ 3693 还要求输出字典序最小的重复次数最多的连续重复子串。
思路 : 论文中的方法太好了...想了半天都没点思路 ... 看了论文中的解法才有点想法.
首先我们要枚举长度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 次。最后看最大值是多少。
穷举长度 L 的时间是 n,每次计算的时间是 n/L。所以整个做法的时间复杂度是 O(n/1+n/2+n/3+……+n/n)=O(nlogn)
其中两个位置的往后匹配只要求原字符串的LCP,往前匹配,我的做法是将原字符串反过来之后贴在原字符串之后,在sa求LCP
POJ 3693 还需要求最小字典序 。我的做法是从第一个匹配的字符开始枚举,即从第一个位置开始匹配,求得的LCP为K,然后我们往后枚举起始点,那么这个LCP就会一步步减少,直到 LCP/L-1 小于原来的值位置,那么取这里面的最小字典序解,才能用来更新答案。
下面是POJ 3693 的代码 :
#include <stdio.h>
#include <string.h>
//#include <string>
#include <algorithm>
//#include <iostream>
using namespace std;
#define maxn 200005
#define maxm 200005
int wa[maxn],wb[maxn],wv[maxn],wt[maxn];
int cmp(int *r,int a,int b,int l)
{return r[a]==r[b]&&r[a+l]==r[b+l];}
void da(char *r,int *sa,int n,int m)
{
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<m;i++) wt[i]=0;
for(i=0;i<n;i++) wt[x[i]=r[i]]++;
for(i=1;i<m;i++) wt[i]+=wt[i-1];
for(i=n-1;i>=0;i--) sa[--wt[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++) wt[i]=0;
for(i=0;i<n;i++) wt[wv[i]]++;
for(i=1;i<m;i++) wt[i]+=wt[i-1];
for(i=n-1;i>=0;i--) sa[--wt[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;
}
int Rank[maxn],height[maxn];
void calheight(char *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;
}
int RMQ[maxn];
int mm[maxn];
int best[20][maxn];
void initRMQ(int n)
{
int i,j,a,b;
for(int i=1;i<=n;i++)
RMQ[i] = height[i] ;
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)
{
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)]);
}
char str[maxn] , str2[maxn] ;
int sa[maxn] ;
char ans[maxn] , tmp[maxn] ;
int main(){
int casn = 1 ;
while( scanf( "%s" , str ) != EOF ) {
if( strcmp( str , "#" ) == 0 ) break;
ans[0] = 254 ;
ans[1] = 0 ;
strcpy( str2 , str ) ;
//strrev( str2 ) ;
int Len ;
int len = Len = strlen( str ) ;
str[len] = '$' ; str[len+1] = 0 ;
//strcat( str , str2 ) ;
for( int i = 0 ; i < len ;i ++ ) {
str[len+i+1] = str2[len-i-1] ;
}
str[len+len+1] = 0 ;
len = strlen( str ) ;
// 最后再加一个之前没有出现过的字符 , 避免了判断边界情况
str[len] = '&' ; str[len+1] = 0 ;
len ++ ;
da( str , sa , len + 1 , 200 ) ;
calheight( str , sa , len ) ;
initRMQ( len ) ;
len -- ;
// 枚举 L
int Max = 0 ;
for( int L = 1 ; L <= Len ; L ++ ) {
for( int i = 0 ; ( i + 1 ) * L < Len ; i ++ ) {
int K1 = lcp( i * L , ( i + 1 ) * L ) ;
int K2 = lcp( len - i * L - 1 , len - ( i + 1 ) * L - 1 ) ;
int K = K1 + K2 ;
if( K > 0 ) K -- ;
int s = i * L - K2 + 1 ;
int cnt = K / L + 1 ;
if( cnt >= Max ) {
//tmp = string( str+s , str + s + cnt * L ) ;
if( cnt > Max ) { Max = cnt ; ans[0] = 254 ; ans[1] = 0 ; }
int j = 0 ;
for( int i = s ; i < s + cnt * L ; i ++ ) {
tmp[j++] = str[i] ;
}
tmp[j] = 0 ;
if( strcmp( ans, tmp ) > 0 ) {
strcpy( ans , tmp ) ;
}
int i = 1 ;
while( ( K - i ) / L + 1 == cnt ) {
tmp[j++] = str[s + cnt * L + i -1] ;
tmp[j] = 0 ;
if( strcmp( ans , tmp + i ) > 0 ) {
strcpy( ans , tmp + i ) ;
}
i ++ ;
}
}
}
}
printf( "Case %d: %s\n" , casn ++ , ans ) ;
}
return 0 ;
}
// ccaaccaaccaacc