[Note]后缀自动机

后缀自动机

代码

#include <cstdio>
#include <algorithm>
#include <cstring>

const int M = 1e6 + 10;
const int N = 5e5 + 10;

char s[N];
int trns[M][26], pa[M], mxl[M], mnl[M], gr[M], ep[M];
int len, n;
int tmp[N], rnk[M];

inline int nwst(int mx, int mn, int *tr, int sl) {
    mxl[n] = mx; mnl[n] = mn; pa[n] = sl;
    for (int i = 0; i < 26; ++i) {
        if (tr == NULL) trns[n][i]=-1;
        else trns[n][i] = tr[i];
    }
    return n++;
}

inline void link(int x, int y) {
    mnl[x] = mxl[y]+1; pa[x] = y; deg[y]++;
}

inline int addc(int c, int u) {
    int z = nwst(mxl[u]+1, -1, NULL, -1);
    gr[z] = 1;
    while (u != -1 && trns[u][c] == -1) {
        trns[u][c] = z;
        u = pa[u];
    }
    if (u == -1) {
        link(z, 0);
        return z;
    }
    int x = trns[u][c];
    if (mxl[x] == mxl[u]+1) {
        link(z, x);
        return z;
    }
    int y = nwst(mxl[u]+1, mnl[x], trns[x], pa[x]);
    link(x, y);
    link(z, y);
    while (u != -1 && trns[u][c] == x) {
        trns[u][c] = y;
        u = pa[u];
    }
    return z;
}

inline void build() {
    int u = nwst(0, 0, NULL, -1);
    for (int i = 0; i < len; ++i) u = addc(s[i]-'a', u);
    
    for (int i = 0; i < n; ++i) tmp[mxl[i]]++;
    for (int i = 1; i <= len; ++i) tmp[i] += tmp[i-1];
    for (int i = 0; i < n; ++i) rnk[tmp[mxl[i]]--] = i;
    for (int i = n; i; --i) {
        int &j = rnk[i];
        ep[j] += gr[j];
        if (pa[j] != -1) ep[pa[j]] += ep[j];
    }
    ep[0] = 0;
}

解释

mxln : \(maxlen\)
mnln : \(minlen\)
ep : \(|endpos|\)
trns : \(trans\)
pa : \(suffixLink\)

应用

不同子串的数目问题

\(\sum_{i\in (0,n)} (maxlen_i-minlen_i+1)\)

最多k长子串问题

\(|endpos|\)即为某个节点所包含的串的出现次数。由于答案数组递减,只需要更新每个状态的\(|maxlen|\)对应的答案,最后再调整答案即可。

for (int i = 1; i < n; ++i) 
    ans[mxln[i]] = std::max(ans[mxln[i]], ep[i]);
for (int i = len-1; i; --i) 
    ans[i] = std::max(ans[i], ans[i+1]);
posted @ 2018-09-06 19:59  wyxwyx  阅读(114)  评论(0编辑  收藏  举报