GDCPC广东省赛-C-Conspicuousness题解

本蒟蒻屑的第一篇题解博客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;
}
posted @ 2021-07-08 23:36  Leins  阅读(327)  评论(0编辑  收藏  举报