转自http://blog.csdn.net/acm_cxlove/article/details/7854526
在后缀数组神文中也这题的题解。
比较容易理解的部分就是枚举长度为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 <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <ctime> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; #define INF 0x3f3f3f3f #define inf -0x3f3f3f3f #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define mem0(a) memset(a,0,sizeof(a)) #define mem1(a) memset(a,-1,sizeof(a)) #define mem(a, b) memset(a, b, sizeof(a)) typedef long long ll; using namespace std; const int maxn=100000+100; char s[maxn],s1[maxn]; int sa[maxn],t[maxn],t2[maxn],c[maxn],n; //构造字符串s的后缀数组,每个字符值必须为0~m-1 void build_sa(int m){ int *x=t,*y=t2; //基数排序 for(int i=0;i<m;i++) c[i]=0; for(int i=0;i<n;i++) c[x[i]=s[i]]++; for(int i=1;i<m;i++) c[i]+=c[i-1]; for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i; for(int k=1;k<=n;k<<=1){ int p=0; //直接利用sa数组排序第二关键字 for(int i=n-k;i<n;i++) y[p++]=i; for(int i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k; //基数排序第一关键字 for(int i=0;i<m;i++) c[i]=0; for(int i=0;i<n;i++) c[x[y[i]]]++; for(int i=1;i<m;i++) c[i]+=c[i-1]; for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i]; //根据sa和y计算新的x数组 swap(x,y); p=1; x[sa[0]]=0; for(int i=1;i<n;i++) x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++; if(p>=n) break; m=p; //下次基数排序的最大值 } } int rank1[maxn],height[maxn]; void getHeight(){ int i,j,k=0; for(i=0;i<=n;i++) rank1[sa[i]]=i; for(i=0;i<n;i++){ if(k) k--; int j=sa[rank1[i]-1]; while(s[i+k]==s[j+k]) k++; height[rank1[i]]=k; } } int dp[maxn][20]; void RMQ_init(){ 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 rmq(int ll,int rr){ int k=0; ll=rank1[ll]; //在这个地方总是出错,需要注意的是,height数组里面的值是后缀的字典序,所以在查找的时候, rr=rank1[rr]; //需要找到其排名,而不是其坐标....... if(ll>rr) swap(ll,rr); ll++; while((1<<(k+1))<=rr-ll+1) k++; return min(dp[ll][k],dp[rr-(1<<k)+1][k]); } int a[maxn]; void debug(){ for(int i = 0 ; i <= n ; i ++) printf("%d ",sa[i]); printf("\n"); for(int i = 0 ; i <= n ; i ++) printf("%d ",rank1[i]); printf("\n"); for(int i = 0 ; i <= n ; i ++) printf("%d ",height[i]); printf("\n"); } int main(){ int case1=1; while(scanf("%s",s)!=EOF){ if(s[0]=='#') break; printf("Case %d: ",case1++); n=strlen(s); s[n]='0'; n++; build_sa(144); n--; getHeight(); //debug(); RMQ_init(); int cnt=0,maxv=0; for(int l=1;l<=n/2;l++){ for(int i=0;i+l<n;i+=l){ if(s[i]!=s[i+l]) //快了几百ms continue; int r=rmq(i,i+l); int step=r/l+1; int k=i-(l-r%l); //k可能小于0 if(k>=0&&r%l){ if(rmq(k,k+l)>=r) step++; } if(step>maxv){ cnt=0; maxv=step; a[cnt++]=l; } else if(step==maxv) 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(rmq(sa[i],sa[i]+l)>=(maxv-1)*l){ len=l; st=sa[i]; break; } } } for(int i=st,j=0;j<len*maxv;j++,i++) printf("%c",s[i]); printf("\n"); } return 0; }