2020 CCPC Wannafly Winter Camp Day2 D. 卡拉巴什的字符串(后缀自动机)

题目:https://ac.nowcoder.com/acm/contest/4010

(葫芦爷nb!)

这题还是很有迷惑性的,现场只有2个队在最后ac,我都没去开这题,赛后发现是个sam的裸题…复制了个板子改了几行10分钟不到就a了。

重要的前置知识:sam上两个节点表示的串其lcp是其parent树上lca。

其实到这里这题就做完了(所以说是裸题嘛!),只要一个sam节点可以作为lca(即有儿子),那就可以把它插入题目要求的集合里,显然mex操作求出来的值随着所取串的增长单调不减,那么每次在上一个求出来的值基础上继续暴力求就好了,复杂度O(n)。

要注意的是,对于parent树上的一般节点,只要他有儿子,他就是一个合格的lca了(他自己和他儿子的lca是他自己),但是root节点不一样,root节点不代表一个后缀,也就是他不能成为自己和另外一个节点的lca,但是可以成为另外两个节点的lca(也就是只有root的度数为2以上才能统计长度为0的lcp啦),这里需要特判。

邦邦的ppt上还有解法二,也大同小异,如果两个后缀存在lcp,那么我们把这两个后缀的开头都删掉一个字母,我们会发现他们仍然存在lcp,长度是删掉之前-1,那么也就是说,你只要发现一个后缀存在长度为x的lcp,那么从长度1到长度x的lcp都是存在的,那么就可以不去暴力维护mex的值,只需要对所有可以作为lca的点表示的长度取个max,再+1就是答案了。

关于具体做法,只需要边加字符,边在构造sam时parent树有修改操作的地方维护就好了。

ac代码:

#include <bits/stdc++.h>

using namespace std;

const int maxn = 1e6 + 5;

int _;
char s[maxn];

struct Sam {
    int next[maxn << 1][26];
    int link[maxn << 1], step[maxn << 1];
    int rcnt;
    unordered_set<int> vis;
    int sz, last, root;

    void init() {
        root = sz = last = 1;
        vis.clear();
        rcnt = 0;
        memset(next[root], 0, sizeof(next[root]));
    }

    void add(int c) {
        int p = last;
        int np = ++sz;
        last = np;


        memset(next[np], 0, sizeof(next[np]));
        step[np] = step[p] + 1;
        while (!next[p][c] && p) {
            next[p][c] = np;
            p = link[p];
        }

        if (p == 0) {
            link[np] = root;
            if (++rcnt > 1) {
                vis.insert(0);
                rcnt = INT_MIN;
            }
        } else {
            int q = next[p][c];
            if (step[p] + 1 == step[q]) {
                link[np] = q;
                if (!vis.count(step[q])) {
                    vis.insert(step[q]);
                }
            } else {
                int nq = ++sz;
                memcpy(next[nq], next[q], sizeof(next[q]));
                step[nq] = step[p] + 1;
                link[nq] = link[q];
                link[q] = link[np] = nq;
                if(!vis.count(step[nq])){
                    vis.insert(step[nq]);
                }
                while (next[p][c] == q && p) {
                    next[p][c] = nq;
                    p = link[p];
                }
            }
        }
    }

    void solve() {
        init();

        int g = 0;
        for (int i = 0, len = strlen(s); i < len; i++) {
            add(s[i] - 'a');

            while (vis.count(g)) {
                ++g;
            }
            printf("%d%c", g, i < len - 1 ? ' ' : '\n');
        }
    }
} sam;

int main() {
    scanf("%d", &_);
    while (_--) {
        scanf("%s", s);
        sam.solve();
    }
    return 0;
}
发布了156 篇原创文章 · 获赞 20 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/Cymbals/article/details/104057431