http://hihocoder.com/contest/hiho123/problem/1
题目1 : 后缀数组四·重复旋律4
-
babbabaabaabaabab
样例输出
-
4
描述
小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为长度为 N 的数构成的数列。小Hi在练习过很多曲子以后发现很多作品中的旋律有重复的部分。
我们把一段旋律称为(k,l)-重复的,如果它满足由一个长度为l的字符串重复了k次组成。 如旋律abaabaabaaba是(4,3)重复的,因为它由aba重复4次组成。
小Hi想知道一部作品中k最大的(k,l)-重复旋律。
输入
一行一个仅包含小写字母的字符串。字符串长度不超过 100000。
输出
一行一个整数,表示答案k。
也就是,给一个字符串,
对于一个子串,会有(K,L)由长度为L的部分重复K次。
求所有子串中最大的k。
首先简化版的问题:
给一个字符串,求最大的循环次数。。虽然kmp也是可以求。。不过kmp的求法不便于后面的扩展
可以用后缀数组求,预处理好lcp,预处理好rmq_lcp
对这个字符串,去枚举L,L当然是长度len的约数啦
对于这个L,如果满足LCP(st,st+L)==len-L,那就有一个k=LCP(st,st+L)+1啦
回到原来的问题,可能这些连续的循环串的起始位置并不在st(起点)或st+L的倍数,这就很麻烦了。。
题解证明了一下
如果最大的k对应的起点X不在st(起点)或st+L的倍数的位置时,我们肯定会找到最接近X的对应的L的倍数的位置P
那么X与P的距离就是 lcp(P,P+L)%L
因此我们就可以得到X的位置
再求一次LCP(X,X+L)/L+1即可
因此总的复杂度就是 第一次枚举可能的长度L(1~n)
对于每个长度L,只需要做n/L次 查询LCP并更新答案 ,O(1)的复杂度
总复杂度O(n/1)+O(n/2)+O(n/3)+O(n/L)+... =O(n*logn);
#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;
}