后缀自动机(SAM)

后缀自动机(SAM)

这里只介绍如何构造。

struct SAMNODE
{
    int l, fa;    
    int trans[BASE];
};

一个结点记录三个东西:

  1. \(l\) 表示当前结点代表最长字符串的长度;
  2. \(fa\) 表示当前结点的父亲(即对应与当前串的最长后缀的另一个 \(\operatorname{endpos}\) 等价类);
  3. \(trans[x]\) 表示转移。

构造

SAM 储存 \(lst\)\(cnt\),和各个结点存在 \(t\) 数组(是个结构体,就是上文的 SAMNODE)。

SAM 的构造是在线的,也就是说,每读入一个字符 \(c\),我们将当前读入的字符添加到原先的 SAM 中,逐一添加最后得到整个串的 SAM。

\(\bullet\) \(cnt\) 为当前结点个数,初始为 \(1\)

\(\bullet\)\(lst\) 表示插入上一个字符之后整个字符串对应的状态(开始时 \(lst=1\),算法最后一步更新 \(lst\))。

\(\bullet\) 创建一个新的状态 \(cur\),将 \(t[cur].l\) 赋值为 \(t[lst].l + 1\),重点就是需要维护出 \(t[cur].fa\)

\(\bullet\) 不断跳 \(lst\) 的父亲,如果没有字符 \(c\) 的转移,就将转移指向 \(cur\),直到我们找到一个状态 \(p\) 其可以通过字符 \(c\) 转移到 \(q\),若不存在 \(p\),则一定会跳到根结点 \(1\),直接将 \(cur\) 父亲设为 \(1\) 即可,若存在 \(p\) 则我们分两种情况讨论。

\(\bullet\)\(t[q].l = t[p].l + 1\),则将 \(cur\) 父亲设为 \(q\) 即可。

\(\bullet\)\(t[q].l \not= t[p].l + 1\),我们此时复制状态 \(q\),创造一个新的状态 \(w\),只需更新 \(t[w].l=t[p].l + 1\),将 \(q\)\(cur\) 的父亲均指向 \(w\)

\(\bullet\) 不断跳 \(p\) 的父亲,若 \(t[p].trans[c] = q\) 则更新其为 \(w\)

设字符集大小为 \(|\Sigma|\),可以证明渐进时间复杂度为 \(\mathcal O(n \log |\Sigma|)\),空间复杂度为 \(\mathcal O(n)\),然而如果字符集足够小,可以不写平衡树,以空间换时间将每个结点的转移存储为长度为 \(|\Sigma|\) 的数组(用于快速查询)和链表(用于快速遍历所有可用关键字)。这样算法时间复杂度为 \(\mathcal O(n)\),空间复杂度为 \(\mathcal O(n \times |\Sigma|)\)

注意:最多会有 \(2n-1\) 个状态,所以空间需要开 \(2n\)

题目链接

#include <bits/stdc++.h>

const int N = 1e6 + 10;
const int BASE = 26;

int siz[2 * N];
int head[2 * N], to[2 * N], nxt[2 * N], tot;
inline void add(int u, int v)
{
    nxt[++ tot] = head[u];
    head[u] = tot;
    to[tot] = v;
}

struct SAMNODE
{
    int l, fa;
    int trans[BASE];
};

struct SAM
{
    int cnt, lst;
    SAMNODE t[N * 2];
    SAM () { cnt = lst = 1; }
    inline void extend(int c)
    {
        int cur = ++ cnt, p = lst; lst = cur;
        // 新建结点 cur
        siz[cur] = 1;
        t[cur].l = t[p].l + 1;
        // 更新 t[cur].l
        for (; p && ! t[p].trans[c]; p = t[p].fa)
            t[p].trans[c] = cur; // 更新 c 的转移
        // 找 p 满足 p 有 c 的转移
        if (p)
        {
            int q = t[p].trans[c];
            if (t[q].l == t[p].l + 1)
                t[cur].fa = q;
            // 若满足 t[q].l = t[p].l + 1 则直接将 t[cur].fa 设为 q
            else
            {
                int w = ++ cnt; 
                t[w] = t[q]; t[w].l = t[p].l + 1;
                // 复制状态 q 并更新 t[w].l
                t[q].fa = t[cur].fa = w;
                // 更新父亲结点
                for (; p && t[p].trans[c] == q; p = t[p].fa)
                    t[p].trans[c] = w; // 更新转移
            }
        }
        else t[cur].fa = 1; // 不存在 p 则将 t[cur].fa 设为根结点 1
    }   
} T;
char s[N]; int n; long long ans;

inline void dfs(int u)
{
    for (int i = head[u]; i; i = nxt[i])
    {
        int v = to[i];
        dfs(v);
        siz[u] += siz[v];
    }
    if (siz[u] > 1) ans = std::max(ans, 1ll * siz[u] * T.t[u].l);
}
int main()
{
    scanf("%s", s + 1);
    n = strlen(s + 1);
    for (int i = 1; i <= n; ++ i)
        T.extend(s[i] - 'a');
    for (int i = 1; i <= T.cnt; ++ i)
        add(T.t[i].fa, i);
    dfs(1);

    printf("%lld\n", ans);

    return 0;
}
posted @ 2022-02-25 21:06  chzhc  阅读(121)  评论(0编辑  收藏  举报
levels of contents