Maximum repetition substring
Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 9083 | Accepted: 2782 |
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这道题在后缀数组的论文里有详细的解释,还有图来供大家理解,可以看看。
/* * POJ 3693 Maximum repetition substring * 寻找重复次数最多的连续子串 * * 后缀数组的论文里面有很详细的解释 * 首先枚举重复出现的子串的长度,然后求它最多能出现多少次。 * 如何求呢,首先长度为l的子串,这个字符串肯定至少出现2次,那么连续的子串肯定包含str[0],str[l],str[2*l] * ....str[k*l],中连续的两个,这样我们同样枚举起始的i,i+l进行前后匹配 * 向后匹配:即求后缀i和i+l的最长公共前缀L,这个可以用RMQ+height数组在O(n*logn)内预处理,O(1)的查询 * 向前匹配:如果上一步得到的L能被l整除,则无需向前匹配,因为前面肯定不可能再匹配到。如果L%l!=0,那么可能 * 起始的位置是在i前面一点,这样我们可能会有更优的解,向前多少呢,因为后面已经有L-L%l的长度是合适的了,如 * 果还有,那只可能往前移动(l-L%l),这样才会增加一个重复的次数,于是我们只需要找到lcp(i-(l-L%l),i-(l-L%l)+l) * 如果这个大于L,说明还存在更多的次数 * 这样就找到了重复子串的长度,由于题目要求输出字典序最小的子串,我们可以遍历sa数组,找到的第一个符合条件的 * 字符串肯定就是我们需要的答案,因为sa数组就是按字典序排序的。 * * 枚举长度的时间是O(n),每次计算的时间是O(n/l)的,这样总的时间复杂度是 * O(n/1+n/2+n/3+....+n/n)=O(n*logn) */ #include <stdio.h> #include <string.h> #include <iostream> using namespace std; const int MAXN = 100000+100; int sa[MAXN]; int t1[MAXN],t2[MAXN],c[MAXN]; int Rank[MAXN],height[MAXN]; void build_sa(int s[],int n,int m) { int i,j,p,*x=t1,*y=t2; for(i=0;i<m;i++)c[i]=0; for(i=0;i<n;i++)c[x[i]=s[i]]++; for(i=1;i<m;i++)c[i]+=c[i-1]; for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i; for(j=1;j<=n;j<<=1) { p=0; for(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<m;i++)c[i]=0; for(i=0;i<n;i++)c[x[y[i]]]++; for(i=1;i<m;i++)c[i]+=c[i-1]; for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i]; swap(x,y); p=1;x[sa[0]]=0; for(i=1;i<n;i++) x[sa[i]]=y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++; if(p>=n)break; m=p; } } void getHeight(int s[],int n) { int i,j,k=0; for(i=0;i<=n;i++) Rank[sa[i]]=i; for(i=0;i<n;i++) { if(k)k--; j=sa[Rank[i]-1]; while(s[i+k]==s[j+k])k++; height[Rank[i]]=k; } } struct SparseTable { int rmq[MAXN]; int mm[MAXN]; int dp[MAXN][20]; void init(int n) { mm[0]=-1; for(int i=1;i<=n;i++) { mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1]; dp[i][0]=i; } for(int j=1;j<=mm[n];j++) for(int i=1;i+(1<<j)-1<=n;i++) dp[i][j]=rmq[dp[i][j-1]]<rmq[dp[i+(1<<(j-1))][j-1]]?dp[i][j-1]:dp[i+(1<<(j-1))][j-1]; } int query(int a,int b) { if(a>b) swap(a,b); int k=mm[b-a+1]; return rmq[dp[a][k]]<=rmq[dp[b-(1<<k)+1][k]]?dp[a][k]:dp[b-(1<<k)+1][k]; } }ST; int lcp(int a,int b) { a=Rank[a],b=Rank[b]; if(a>b) swap(a,b); return height[ST.query(a+1,b)]; } char str[MAXN]; int ss[MAXN]; int a[MAXN]; int main() { int iCase=0; while(scanf("%s",str)==1) { if(str[0]==‘#‘) break; iCase++; int n=strlen(str); for(int i=0;i<=n;i++) ss[i]=str[i]; build_sa(ss,n+1,128); getHeight(ss,n); for(int i=1;i<=n;i++) ST.rmq[i]=height[i]; ST.init(n); int cnt=0,ma=0; for(int l=1;l<=n;l++)//枚举重复子串的长度 { for(int i=0;i+l<n;i+=l) { int ll=lcp(i,i+l); int num=ll/l+1; int pre=i-(l-ll%l);//往前找 if(pre>=0&&ll%l) { if(lcp(pre,pre+l)>=ll) num++; } if(num>ma) { ma=num; cnt=0; a[cnt++]=l;//不同长度可能出现了同样的次数 } else if(num==ma) a[cnt++]=l; } } int len=-1,st; for(int i=1;i<=n&&len==-1;i++) { for(int j=0;j<cnt;j++) { int l=a[j]; if(lcp(sa[i],sa[i]+l)>=(ma-1)*l)//在sa数组找到的第一个就是字典序最小的 { len=l; st=sa[i]; break; } } } str[st+len*ma]=0; printf("Case %d: %s\n",iCase,str+st); } return 0; }