problem
给定一个长度为 n n n 的字符串。在恰好出现了 k k k 次的子串中,按照子串的长度分类,请求出子串数量最多的那一类的长度(如果有多个输出最长长度)。
有 T T T 组数据。
数据范围: 1 ≤ n ≤ 1 0 5 1≤n≤10^5 1≤n≤105, 1 ≤ T ≤ 100 1≤T≤100 1≤T≤100, ∑ n ≤ 3 × 1 0 6 \sum n≤3\times10^6 ∑n≤3×106。
solution
这其实就是一道 SAM 的模板题。
SAM 中的 S i z e i Size_i Sizei(除去克隆点后 i i i 子树大小)有一个很好的性质,即 S i z e i Size_i Sizei 等于 i i i 的 e n d p o s \mathrm{endpos} endpos 集合中子串的出现次数。
所以满足 S i z e i = k Size_i=k Sizei=k 的点 i i i 就是我们要找的点。
那么对于 S i z e i = k Size_i=k Sizei=k 的点 i i i,它 e n d p o s \mathrm{endpos} endpos 集合内串长范围是 ( l e n f a i , l e n i ] (len_{fa_i},len_i] (lenfai,leni],所以我们差分后求个 m a x max max 即可。
时间复杂度 O ( n ) O(n) O(n)。
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
using namespace std;
char S[N];
int k,tot,last,num,Size[N],sum[N],Sort[N],A[N];
struct SAM{
int len,link,nxt[26];
}a[N];
void Clear(){
memset(sum,0,sizeof(sum));
memset(Size,0,sizeof(Size));
memset(a,0,sizeof(a));
memset(A,0,sizeof(A));
num=tot=last=0,a[0].link=-1;
}
void Insert(int c){
int p,cur=++tot;
a[cur].len=a[last].len+1,Size[cur]=1;
for(p=last;p!=-1&&!a[p].nxt[c];p=a[p].link) a[p].nxt[c]=cur;
if(p==-1) a[cur].link=0;
else{
int now=a[p].nxt[c];
if(a[p].len+1==a[now].len) a[cur].link=now;
else{
int Clone=++tot;
a[Clone]=a[now],a[Clone].len=a[p].len+1;
for(;p!=-1&&a[p].nxt[c]==now;p=a[p].link) a[p].nxt[c]=Clone;
a[cur].link=a[now].link=Clone;
}
}
last=cur;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
Clear();
scanf("%s%d",S+1,&k);
int l=strlen(S+1);
for(int i=1;i<=l;++i) Insert(S[i]-'a');
for(int i=1;i<=tot;++i) sum[a[i].len]++;
for(int i=1;i<=tot;++i) sum[i]+=sum[i-1];
for(int i=1;i<=tot;++i) Sort[sum[a[i].len]--]=i;
for(int i=tot;i>=1;--i) Size[a[Sort[i]].link]+=Size[Sort[i]];
for(int i=1;i<=tot;++i)
if(Size[i]==k) A[a[a[i].link].len+1]++,A[a[i].len+1]--,num++;
int ans=0,Max=0;
for(int i=1;i<=tot;++i){
A[i]+=A[i-1];
if(Max<=A[i]) Max=A[i],ans=i;
}
printf("%d\n",num?ans:-1);
}
return 0;
}