[TJOI2019]甲苯先生和大中锋的字符串——后缀自动机+差分

时间:2023-03-08 19:59:32

题目链接:

[TJOI2019]甲苯先生和大中锋的字符串

对原串建后缀自动机并维护$parent$树上每个点的子树大小,显然子树大小为$k$的节点所代表的子串出现过$k$次,那么我们需要将$[len[fa[i]]+1,len[i]]$这一段区间的数目都$+1$,只需要差分即可,最后求前缀和并求出所有前缀和的最大值的位置即为答案。

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<vector>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int T;
char ch[100010];
int n,k;
int s[100010];
int last;
int tr[200010][26];
int len[200010];
int pre[200010];
int head[200010];
int next[200010];
int to[200010];
int tot;
int cnt;
int size[200010];
void add(int x,int y)
{
next[++tot]=head[x];
head[x]=tot;
to[tot]=y;
}
void dfs(int x)
{
for(int i=head[x];i;i=next[i])
{
dfs(to[i]);
size[x]+=size[to[i]];
}
}
void init()
{
memset(s,0,sizeof(s));
memset(tr,0,sizeof(tr));
memset(len,0,sizeof(len));
memset(pre,0,sizeof(pre));
memset(head,0,sizeof(head));
memset(size,0,sizeof(size));
tot=0;
last=cnt=1;
}
void insert(int x)
{
int p=last;
int np=++cnt;
last=np;
size[np]++;
len[np]=len[p]+1;
for(;p&&!tr[p][x];p=pre[p])
{
tr[p][x]=np;
}
if(!p)
{
pre[np]=1;
}
else
{
int q=tr[p][x];
if(len[q]==len[p]+1)
{
pre[np]=q;
}
else
{
int nq=++cnt;
len[nq]=len[p]+1;
pre[nq]=pre[q];
memcpy(tr[nq],tr[q],sizeof(tr[q]));
pre[np]=pre[q]=nq;
for(;p&&tr[p][x]==q;p=pre[p])
{
tr[p][x]=nq;
}
}
}
}
void solve()
{
scanf("%s%d",ch+1,&k);
n=strlen(ch+1);
for(int i=1;i<=n;i++)
{
insert(ch[i]-'a');
}
for(int i=2;i<=cnt;i++)
{
add(pre[i],i);
}
dfs(1);
for(int i=1;i<=cnt;i++)
{
if(size[i]==k)
{
s[len[pre[i]]+1]++;
s[len[i]+1]--;
}
}
int sum=0;
int mx=0;
int ans=0;
for(int i=1;i<=n;i++)
{
sum+=s[i];
if(sum>=mx)
{
mx=sum,ans=i;
}
}
printf("%d\n",mx>0?ans:-1);
}
int main()
{
scanf("%d",&T);
while(T--)
{
init();
solve();
}
}