SPOJ - REPEATS 687:
题意:找到字符串中重复次数最多的连续重复子串,输出这个次数
分析:
Ⅰ:考虑枚举循环节的长度 L,若我们知道这个连续重复子串的起点 s ,则
子串长度 (因为结尾可能有少于L的多余的字符,所以需要向下取整)
次数即 。 (+L是因为还要算上区间[s,s+L],下文+1同理)
但是如果我们枚举 L 再内层枚举起点 s,复杂度就是O(n^2) ,需要优化。
Ⅱ:考虑枚举 L的时候,把整个字符串分成若干个长度为L的区间 [1,L],[L+1,2*L]... 枚举所有区间的左边界 i:
① 若 lcp(i,i+L)%L==0,为 L 的整倍数,则证明 i 就是起点,重复次数即 ( lcp(i,i+L) + L) / L 。
② 其余情况,令 len=lcp(i,i+L),则这个子串至少有 个循环节,最后多出来 m=len%L个字符,即
等价于 S[i,i+m-1] :
1)若 S[i+m-L,i+m-1] = S[i+m,i+m+L-1] ,则正好可以多构成一个循环节,此时判断 lcp(i+m-L,i+m-1) == len+(L-m);
2)若存在 h<m,使得 S[i+h-L,i+h-1] = S[i+h,i+h+L-1] (即最后m个字符的后面m-h个是多余的),则有
lcp(i+h-L,i+h-1) == len+(L-h),根据后缀数组的性质,我们往右平移 m-h 位即 :
--- lcp(i+h-L,i+h-1) => lcp(i+h-L+m-h,i+h-1+m-h) => lcp(i+m-L,i+m-1)
--- len+(L-h) => len+(L-h)-(m-h) => len+(L-m),
则 1)和 2)可以合并成一类情况,我们只需要判断 lcp(i+m-L,i+m-1) == len+(L-m) 即可,这里我们还可以简化成
lcp(i+m-L,i+m-1) >=L 即可,因为有 len-m>=0 ;
代码:
#include<cctype>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 5E4+10;
char str[N];
int n,s[N];
int sa[N],rk[N],oldrk[N<<1],px[N],id[N],cnt[N],ht[N];
bool cmp(int x,int y,int w){
return oldrk[x]==oldrk[y] && oldrk[x+w]==oldrk[y+w];
}
void da(int s[],int n,int m){
int i,p=0,w,k;
for(i=1;i<=n;i++) ++cnt[rk[i] = s[i]];
for(i=1;i<=m;i++) cnt[i] += cnt[i-1];
for(i=n;i>=1;i--) sa[cnt[rk[i]]--] = i;
for(w=1;w<n;w<<=1,m=p){
for(p=0,i=n;i>n-w;i--) id[++p]=i;
for(i=1;i<=n;i++)
if(sa[i]>w) id[++p]=sa[i]-w;
memset(cnt,0,sizeof(cnt));
for(i=1;i<=n;i++) ++cnt[px[i] = rk[id[i]]];
for(i=1;i<=m;i++) cnt[i] += cnt[i-1];
for(i=n;i>=1;i--) sa[cnt[px[i]]--] = id[i];
memcpy(oldrk,rk,sizeof(rk));
for(p=0,i=1;i<=n;i++)
rk[sa[i]]=cmp(sa[i],sa[i-1],w)?p:++p;
}
for(i=1,k=0;i<=n;i++){
if(k) --k;
while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
ht[rk[i]]=k;
}
}
int f[N][30];
void init_rmq(){
int i,j;
for(i=1;i<=n;i++) f[i][0]=ht[i];
for(j=1;(1<<j)<=n;j++){
for(i=1;i+(1<<j)-1<=n;i++){
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
}
int rmq(int l,int r){
if(l>r) swap(l,r);
int k=0;
while((1<<(k+1))<=r-l+1) k++;
return min(f[l][k],f[r-(1<<k)+1][k]);
}
int lcp(int l,int r){
int a=rk[l],b=rk[r];
if(a>b) swap(a,b);
return rmq(a+1,b);
}
int main()
{
int T; scanf("%d",&T);
while(T--){
memset(cnt,0,sizeof(cnt));
scanf("%d",&n);
for(int i=1;i<=n;i++){
char c;
while(1){
c=getchar();
if(isalpha(c)) break;
}
str[i]=c;
}
for(int i=1;i<=n;i++) s[i]=str[i];
da(s,n,300);
init_rmq();
int ANS=1;
for(int L=1;L<n;L++){
for(int i=1;i+L<=n;i+=L){
int len=lcp(i,i+L);
int k=len/L+1;
int s=i-(L-len%L);
if(s&&lcp(s,s+L)>=L) k++; //可以多构成一个循环节
ANS=max(ANS,k);
}
}
printf("%d\n",ANS);
}
}
POJ - 3693 (Maximum repetition substring)
题意:找到字符串中重复次数最多的连续重复子串,输出这个子串,多解时输出字典序最小答案
分析:道理和前面一题一个样,只是要输出具体且字典序最小的,则我们可以先保存最高次数Max情况下所有可能的循环节长度L,然后根据 sa 数组已经按照字典序排好序的性质,从前到后找到第一个满足条件的答案就可以了
代码:
#include<vector>
#include<cctype>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 1E5+10;
char str[N];
int n,s[N];
int sa[N],rk[N],oldrk[N<<1],px[N],id[N],cnt[N],ht[N];
bool cmp(int x,int y,int w){
return oldrk[x]==oldrk[y] && oldrk[x+w]==oldrk[y+w];
}
void da(int s[],int n,int m){
int i,p=0,w,k;
for(i=1;i<=n;i++) ++cnt[rk[i] = s[i]];
for(i=1;i<=m;i++) cnt[i] += cnt[i-1];
for(i=n;i>=1;i--) sa[cnt[rk[i]]--] = i;
for(w=1;w<n;w<<=1,m=p){
for(p=0,i=n;i>n-w;i--) id[++p]=i;
for(i=1;i<=n;i++)
if(sa[i]>w) id[++p]=sa[i]-w;
memset(cnt,0,sizeof(cnt));
for(i=1;i<=n;i++) ++cnt[px[i] = rk[id[i]]];
for(i=1;i<=m;i++) cnt[i] += cnt[i-1];
for(i=n;i>=1;i--) sa[cnt[px[i]]--] = id[i];
memcpy(oldrk,rk,sizeof(rk));
for(p=0,i=1;i<=n;i++)
rk[sa[i]]=cmp(sa[i],sa[i-1],w)?p:++p;
}
for(i=1,k=0;i<=n;i++){
if(k) --k;
while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
ht[rk[i]]=k;
}
}
int f[N][30];
void init_rmq(){
int i,j;
for(i=1;i<=n;i++) f[i][0]=ht[i];
for(j=1;(1<<j)<=n;j++){
for(i=1;i+(1<<j)-1<=n;i++){
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
}
int rmq(int l,int r){
if(l>r) swap(l,r);
int k=0;
while((1<<(k+1))<=r-l+1) k++;
return min(f[l][k],f[r-(1<<k)+1][k]);
}
int lcp(int l,int r){
int a=rk[l],b=rk[r];
if(a>b) swap(a,b);
return rmq(a+1,b);
}
vector<int>vt;
int main()
{
int T=0;
while(scanf("%s",str+1)){
memset(cnt,0,sizeof(cnt));
n=strlen(str+1);
if(n==1&&str[1]=='#') break;
for(int i=1;i<=n;i++) s[i]=str[i];
da(s,n,128);
init_rmq();
int Max=0;
for(int L=1;L<=n;L++){
for(int i=1;i+L<=n;i+=L){
int len=lcp(i,i+L);
int k=len/L+1;
int s=i-(L-len%L);
if(s&&lcp(s,s+L)>=L){
k++;
}
else{
s=i;
}
if(Max<k){
Max=k;
vt.clear();
vt.push_back(L);
}
else if(Max==k){
vt.push_back(L);
}
}
}
printf("Case %d: ",++T);
bool flag=0;
for(int i=1;i<=n;i++){
for(int j=0;j<vt.size();j++){
int LCP=lcp(sa[i],sa[i]+vt[j]);
if(LCP/vt[j]+1==Max){
for(int k=0;k<Max*vt[j];k++) putchar(str[sa[i]+k]);
flag=1;
break;
}
}
if(flag) break;
}
puts("");
}
}