这道题是后缀数组的一个典型应用。求一个串中连续重复次数最多的一个子串。做这道题的时候要把握住,连续重复 和 次数最多这两个关键,才能有突破。
下面的思路是照搬大牛的:(大神不要打我)
在后缀数组神文中有这题的题解。
比较容易理解的部分就是枚举长度为L,然后看长度为L的字符串最多连续出现几次。
既然长度为L的串重复出现,那么str[0],str[l],str[2*l]……中肯定有两个连续的出现在字符串中。
那么就枚举连续的两个,然后从这两个字符前后匹配,看最多能匹配多远。
即以str[i*l],str[i*l+l]前后匹配,这里是通过查询suffix(i*l),suffix(i*l+l)的最长公共前缀
通过rank值能找到i*l,与i*l+l的排名,我们要查询的是这段区间的height的最小值,通过RMQ预处理
达到查询为0(1)的复杂度,
设LCP长度为M, 则答案显然为M / L + 1, 但这不一定是最好的, 因为答案的首尾不一定再我们枚举的位置上. 我的解决方法是, 我们考虑M % L的值的意义, 我们可以认为是后面多了M % L个字符, 但是我们更可以想成前面少了(L - M % L)个字符! 所以我们求后缀j * L - (L - M % L)与后缀(j + 1) * L - (L - M % L)的最长公共前缀。
即把之前的区间前缀L-M%L即可。
然后把可能取到最大值的长度L保存,由于 题目要求字典序最小,通过sa数组进行枚举,取到的第一组,肯定是字典序最小的。
我的代码如下:
#include<iostream>
#include<string.h>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define MAXD 100010
#define MAXL 300
using namespace std;
int r[MAXD], Rank[MAXD], height[MAXD];
int sa[MAXD], wa[MAXD], wb[MAXD], WS[MAXL], wv[MAXD]; //WS 的大小是最大字符的大小。其他的是串长大小
//r数组存放字符的值
int cmp(int *p, int x, int y, int l)
{
return p[x] == p[y] && p[x + l] == p[y + l];
}
void da(int n, int m) //n为字符串长度,m为字符的最大值
{
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(p = 1, j = 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, x[sa[0]] = 0, p = 1, i = 1; i < n; i ++)
x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1: p ++;
}
}
void calheight(int n) //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 ++);
}
/*****************以上都是后缀数组模板,下面的是具体问题的处理。******************/
char str[MAXD];
int dp[MAXD][20];
int cnt=0,maxtime=0;
void RMQ(int n){
for(int i = 1; i <= n; i++)
dp[i][0] = height[i];
for(int j = 1; (1<<j)<=n; j++)
for(int i = 1; i+(1<<j)-1<=n; i++)
dp[i][j] = min( dp[i][j-1], dp[i+(1<<(j-1))][j-1] );
}
int query(int L,int R){
if(L>R) swap(L,R); L++;
int k = (int)floor( log(1.*(R-L+1))/log(2.0) );
return min( dp[L][k], dp[R-(1<<k)+1][k] );
}
/*
void RMQ(int num) //num为串长减一
{
int i,j;
int k=log(num+0.0)/log(2.0);
for(i=1;i<=num;i++) dp[i][0]=height[i]; //根据具体情况初始化
for(int j = 1; j<=k; ++j)
for(int i = 1; i<= num; ++i)
if(i+(1<<j)-1<=num)
{
dp[i][j] = min(dp[i][j-1], dp[i+(1<<(j-1))][j-1]);
}
}
int query(int l,int R)
{
int a=Rank[l],b=Rank[R];
if(a>b) swap(a,b);
a++; //要注意这个细节,例如,sa[1]~sa[4]的最小公用前缀,是height[2]~height[4]的最小值。这样就理解为什么加一了。
int k=log(double(b-a+1))/log(2.0);
if(dp[a][k]<=dp[b-(1<<k)+1][k])
return dp[a][k];
else return dp[b-(1<<k)+1][k];
} */
int main()
{
int i,j;
int n;
int t=0;
while(scanf("%s",str))
{
if(strcmp(str,"#")==0) break;
t++;
printf("Case %d: ",t);
n=strlen(str);
for(i=0;i<n;i++)
r[i]=str[i];
r[n]=0;
n++;
memset(Rank,0,sizeof(Rank)); //一定要重新初始化,否则会产生访问上一次结果的可能。
da(n,300); //n为修改后的串长
calheight(n-1);
RMQ(n-1);
cnt=0;
maxtime=0;
int a[MAXD];
for(i=1;i<n-1;i++)
{
for(j=0;j+i<n-1;j+=i)
{
int M=query(Rank[j],Rank[j+i]);
int time=M/i+1;
int temp=j-(i-M%i);
if(temp>=0&&M%i)
if(query(Rank[temp],Rank[temp+i])/i>=time)
time++;
if(time>maxtime)
{
maxtime=time;
cnt=0;
a[cnt++]=i;
}
else if(time==maxtime)
{
a[cnt++]=i;
}
}
}
if(cnt==0||maxtime==1)
{
printf("%c\n", str[sa[1]] );
continue;
}
int start=0,len=-1;
for(i=1;i<n&&len==-1;i++)
{
for(j=0;j<cnt;j++)
{
if(query(Rank[sa[i]],Rank[sa[i]+a[j]])>=a[j]*(maxtime-1))
{
start=sa[i];
len=a[j];
break;
}
}
}
for(i=start;i<len*maxtime+start;i++) printf("%c",str[i]);
printf("\n");
}
return 0;
}