本蒟蒻屑的第一篇题解博客Qwq
题目翻译:给定一个由1-9组成的长度范围为(3e5)的字符串,并给出q(q <= 3e5)次询问,每次给出个长度k, 求出该字符串中长度至少为k的子串的最长长度。
省赛的C题,比赛时读题发现应该是一个后缀自动机但由于本屑非oier,对于xx自动机也只是听过没有了解,当场就放弃了,近几天学了SAM的黑盒使用后,终于A了此题。
思路:众所周知,对一个字符串建起来SAM后,SAM上每个结点都有个l数组,l数组代表的是该节点所包含的字符串集合中,最长字符串的长度,而每个节点有size数组,表示该字符串集合出现的次数。这时候就有个较为朴素的思路:对于每次询问,枚举所有tot的节点i,当l[i] >= k时,则ans = max(l[i], ans); 但这样明显时间复杂度会超,因此,我们可以预处理出每个点的答案,最后一并询问。
我们开两个数组,dp数组和ans数组,其中ans数组为我们要求的答案,而dp数组定义如下:
dp[i] 表示 ans[1] ~ ans[i] 至少应该为几,
在枚举每个节点的时候,该节点代表的字符串集合中,最长的长度为l[i],则ans[1] ~ ans[l[i]] 至少为size[i];
则有如下代码:
for (int i = 1; i <= tot; i++) {
dp[l[i]] = max(dp[l[i]], (ll)size[i]);
}
之后,我们一步一步求ans数组
显然有,dp[i]为长度为i的子串最大出现次数
则有从后往前推的递推式,ans[i] = max(dp[i], ans[i + 1]);
则有如下代码
for (int i = n - 1; i >= 1; i--) {
ans[i] = max(dp[i], ans[i + 1]);
}
完整代码如下
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e5 + 10;
char s[N];
int n, K, q;
ll dp[N];
ll ans[N];
struct SuffixAutoMaton {
int ch[N << 1][10], fa[N << 1],l[N << 1],size[N << 1],k[N << 1],c[N << 1];
int last, tot;
void init() { last = tot = 1;memset(ch[1], 0, sizeof ch[1]); }
void ins(int c,int pos) {
int p = last,np = ++tot;last = np;l[np] = l[p] + 1;
memset(ch[tot],0,sizeof ch[tot]);
for(;p && !ch[p][c]; p = fa[p])ch[p][c] = np;
if(!p)fa[np] = 1;
else {
int q = ch[p][c];
if(l[p] + 1 == l[q]) fa[np] = q;
else {
int nq = ++tot; l[nq] = l[p] + 1;
memcpy(ch[nq], ch[q], sizeof(ch[q]));
fa[nq] = fa[q]; fa[q] = fa[np] = nq;
for(;ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
}
}
size[np] = 1;
}
void build() {
init();
int n = strlen(s + 1);
for(int i = 1;i <= n; i++) ins(s[i]-'0',i);
for(int i = 1;i <= tot; i++) c[l[i]]++;
for(int i = 1;i <= tot; i++) c[i] += c[i-1];
for(int i = 1;i <= tot; i++) k[c[l[i]]--] = i;
for(int i = tot; i >= 1; i--) {
int id = k[i];
size[fa[id]] += size[id];
}
for (int i = 1; i <= tot; i++) {
dp[l[i]] = max(dp[l[i]], (ll)size[i]); // 求出dp数组。
}
}
}sam;
int main() {
cin >> n >> K;
scanf("%s", s + 1);
sam.build();
ans[n] = dp[n];
for (int i = n - 1; i >= 1; i--) {
ans[i] = max(dp[i], ans[i + 1]); //求出ans数组。
}
for (int i = 1; i <= K; i++) {
scanf("%lld", &q);
printf("%lld\n", ans[q]);
}
return 0;
}