【洛谷P5341】甲苯先生和大中锋的字符串
题目
题目链接:https://www.luogu.com.cn/problem/P5341
大中锋有一个长度为 \(n\) 的字符串,他只知道其中的一个子串是祖上传下来的宝藏的密码。但是由于字符串很长,大中锋很难将这些子串一一尝试。
这天大中锋找到甲苯先生算命,但是甲苯先生说:“天机不可泄漏”。
在大中锋的苦苦哀求下,甲苯先生告诉大中锋:“密码是在字符串中恰好出现了 \(k\) 次的子串”。
但是大中锋不知道该怎么做,在大中锋再三的恳求下,甲苯先生看其真诚,又告诉他:“在恰好出现了 \(k\) 次的子串中,你去按照字串的长度分类,密码就在数量最多的那一类里”。
大中锋为了尝试这个密码,想让你帮忙找出子串长度出现次数最多的长度数(如果有多个输出最长长度)。
\(1\leq n\leq 10^5,1 \leq T \leq 100,\sum n \leq 3 * 10^6\)。
思路
氵氵氵。
恰好出现 \(k\) 次的子串显然等价于 \(\mathrm{endpos}\) 集合大小等于 \(k\) 的子串,parent 树上乱搞就可以得到每一个节点 \(\mathrm{endpos}\) 集合的大小。
然后如果一个等价类的出现次数恰好为 \(k\),那么直接用差分把 \(cnt_{\mathrm{len[fa]+1}}\sim cnt_{\mathrm{len[x]}}\) 全部加一。最后在 \(cnt\) 中找最大值即可。
时间复杂度 \(O(\sum n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=200010;
int Q,n,m,ans,cnt[N],a[N],c[N];
char s[N];
struct SAM
{
int tot,last,ch[N][26],fa[N],len[N],siz[N];
void ins(int c)
{
int p=last,np=++tot;
last=tot; len[np]=len[p]+1; siz[np]=1;
for (;p && !ch[p][c];p=fa[p]) ch[p][c]=np;
if (!p) fa[np]=1;
else
{
int q=ch[p][c];
if (len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
fa[nq]=fa[q]; len[nq]=len[p]+1;
for (int i=0;i<26;i++) ch[nq][i]=ch[q][i];
fa[q]=fa[np]=nq;
for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
void topsort()
{
for (int i=0;i<=tot;i++) c[i]=0;
for (int i=1;i<=tot;i++) c[len[i]]++;
for (int i=1;i<=tot;i++) c[i]+=c[i-1];
for (int i=1;i<=tot;i++) a[c[len[i]]--]=i;
for (int i=tot;i>=1;i--)
{
int j=a[i];
siz[fa[j]]+=siz[j];
if (j!=1 && siz[j]==m) cnt[len[fa[j]]+1]++,cnt[len[j]+1]--;
}
}
}sam;
void prework()
{
for (int i=0;i<=sam.tot;i++)
{
sam.fa[i]=sam.len[i]=sam.siz[i]=0;
memset(sam.ch[i],0,sizeof(sam.ch[i]));
}
sam.tot=sam.last=1;
for (int i=0;i<=n;i++) cnt[i]=0;
}
int main()
{
scanf("%d",&Q);
while (Q--)
{
prework();
scanf("%s%d",s+1,&m);
n=strlen(s+1);
for (int i=1;i<=n;i++)
sam.ins(s[i]-'a');
sam.topsort();
ans=0;
for (int i=1;i<=n;i++)
{
cnt[i]+=cnt[i-1];
if (cnt[i] && cnt[i]>=cnt[ans]) ans=i;
}
if (ans) printf("%d\n",ans);
else printf("-1\n");
}
return 0;
}