• 数据结构 题解


    一、题目:


    二、思路:

    一道非常好的后缀树相关题目。

    先贴一张后缀树的图片:

    从根下来的一条路径所对应的子串都是不同的。

    首先我们考虑后缀树上从父亲到某个儿子的一条边 ((x,y)),这条边肯定会包含若干个字母。举个例子,比如边 ((1,6)) 就包含了 (mathtt{bbaa}) 这四个字母。那么,如果 (A) 是后缀树从根下来,到这条边的某个字母所对应的子串,(B) 是这条边剩余的字母组成的子串,那么我们可以肯定,所有 (A) 出现过的位置后面一定跟的是 (B)

    比如就拿 ((1,6)) 边来举例子。那么就有

    A B
    (mathtt{ab}) (mathtt{baa})
    (mathtt{abb}) (mathtt{aa})
    (mathtt{abba}) (mathtt{a})

    假设一条边上字母的个数为 (len),所以一条边可以贡献的答案就是 (1+2+cdots+len-1),即 (dfrac{len imes(len - 1)}{2})

    但是这些还不够,题目中说,那些 (A) 后面不够 (B) 的长度的位置可以不管,也就是说,不一定必须要对于 (A) 出现的所有位置,(B) 都要跟在后面,允许一些位置后面不跟 (B)。这些 (A) 后面是可以跟不同的子串的,对应在后缀树上就是一个分叉点把 (A)(B) 分开了。

    (mathtt{b}) 作为 (A) 来举例子。(mathtt{b}) 对应的节点是 5 号节点。我们可以发现,5 号节点的子树中长度大于 3 的子串还是可以作为 (B) 的。这个 3 是怎么来的呢?其实就是 5 号点的子树中次深的节点到 5 号节点的距离,即 4 号节点到 5 号点的距离。

    具体一点,合法的情况有

    A B
    (mathtt{b}) (mathtt{abba})
    (mathtt{b}) (mathtt{abbaa})

    当然,比如说 ((S,5)) 这条边有两个字母 (mathtt{ab}),那么 (A) 就可以是 (mathtt{a}),也可以是 (mathtt{ab}) 了。如果(A)(mathtt{a}),那么只需要在每个 (B) 前添一个 (mathtt{b}) 即可。说简单一点,就是 (A)(B) 可以不在同一条边中,而是在同一条边中断开,但是 (B) 一定要对应着最深节点除去次深节点的部分。那么这一条边又可以贡献 (len imes(mathbb{maxdep}-mathbb{secdep}))

    三、代码:

    #include <iostream> // 由于我的代码使用后缀自动机来建后缀树,所以可能会有不分叉的节点,因此特判会多一些。
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    typedef long long LL;
    #define FILEIN(s) freopen(s, "r", stdin)
    #define FILEOUT(s) freopen(s, "w", stdout)
    #define mem(s, v) memset(s, v, sizeof s)
    
    inline int read(void) {
        int x = 0, f = 1; char ch = getchar();
        while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
        while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
        return f * x;
    }
    
    const int MAXN = 400005;
    const int INF = 1e8;
    
    int n, tot = 1, las = 1;
    LL ans;
    int maxd[MAXN], sec[MAXN], head[MAXN], idx;
    int dep[MAXN];
    char ch[MAXN];
    
    struct Node {
        int ch[27];
        int fa, maxlen;
    }node[MAXN << 1];
    
    struct Edge {
        int y, next;
        Edge() {}
        Edge(int _y, int _next) : y(_y), next(_next) {}
    }e[MAXN << 1];
    
    inline void connect(int x, int y) {
        e[++idx] = Edge(y, head[x]);
        head[x] = idx;
    }
    
    void extend(int c) {
        int z = ++tot, v = las; las = tot;
        node[z].maxlen = node[v].maxlen + 1;
        for (; v && node[v].ch[c] == 0; v = node[v].fa) node[v].ch[c] = z;
        if (!v) node[z].fa = 1;
        else {
            int x = node[v].ch[c];
            if (node[x].maxlen == node[v].maxlen + 1) node[z].fa = x;
            else {
                int y = ++tot;
                node[y] = node[x];
                node[y].maxlen = node[v].maxlen + 1;
                node[x].fa = node[z].fa = y;
                for (; v && node[v].ch[c] == x; v = node[v].fa) node[v].ch[c] = y;
            }
        }
    }
    
    void dfs1(int x) {
        int len = node[x].maxlen - node[node[x].fa].maxlen;
        dep[x] = dep[node[x].fa] + len;
        ans += 1LL * len * (len - 1) / 2;
        for (int i = head[x]; i; i = e[i].next) {
            int y = e[i].y;
            dfs1(y);
        }
    }
    
    void dfs2(int x) {
        if (head[x]) maxd[x] = -INF;
        sec[x] = -INF;
        for (int i = head[x]; i; i = e[i].next) {
            int y = e[i].y;
            dfs2(y);
            // maxd[x] = max(maxd[x], maxd[y] + node[y].maxlen - node[x].maxlen);
            int len = node[y].maxlen - node[x].maxlen;
            if (maxd[y] + len > maxd[x]) {
                sec[x] = maxd[x];
                maxd[x] = maxd[y] + len;
            }
            else sec[x] = max(sec[x], maxd[y] + len);
    
            sec[x] = max(sec[x], sec[y] + len);
        }
        int len = node[x].maxlen - node[node[x].fa].maxlen;
        ans += 1LL * len * (maxd[x] - max(0, sec[x]));
    }
    
    int main() {
        //FILEIN("in.txt");
        n = read();
        scanf("%s", ch + 1);
        reverse(ch + 1, ch + n + 1);
        for (int i = 1; i <= n; ++i) {
            extend(ch[i] - 'a' + 1);
        }
        for (int i = 2; i <= tot; ++i) {
            connect(node[i].fa, i);
        }
        dfs1(1);
        dfs2(1);
        printf("%lld
    ", ans);
        return 0;
    }
    
  • 相关阅读:
    Vim快捷键键位图大全
    Docker快速入门
    针对base64编码和URIEncode的一点研究
    JavaEE初学笔记之Servlet与Tomcat
    【编码】彻底弄懂ASCII、Unicode、UTF-8之间的关系
    React的世界观及与Vue之比较
    彻底搞懂CSS文本、空白换行问题
    Vue实现懒加载的基本思路
    CSS中的px与物理像素、逻辑像素、1px边框问题
    Vue插值文本换行问题
  • 原文地址:https://www.cnblogs.com/little-aztl/p/15021872.html
Copyright © 2020-2023  润新知