【TJOI 2019】甲苯先生和大中锋的字符串

传送门


problem

给定一个长度为 n n n 的字符串。在恰好出现了 k k k 次的子串中,按照子串的长度分类,请求出子串数量最多的那一类的长度(如果有多个输出最长长度)。

T T T 组数据。

数据范围: 1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105 1 ≤ T ≤ 100 1≤T≤100 1T100 ∑ n ≤ 3 × 1 0 6 \sum n≤3\times10^6 n3×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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值