您的位置:首页 > 其它

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

2016-09-01 17:47 561 查看


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

ccabababc
daabbccaa
#

 

Sample Output

Case 1: ababab
Case 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
= 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
=0(小于前面出现过的字符)
/*
DA(aa,sa,n+1,200);
calheight(aa,sa,n);
*/
int wa
,wb
,ws
,wv
;
int Rank
;//后缀i在sa[]中的排名
int height
;//sa[i]与sa[i-1]的LCP
int sa
;//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
;
int aa
;
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
;
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
=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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  acm 算法