【字符串】后缀自动机

struct SAM {

    static const int MAXN = 1e6 + 10;

    int nxt[2 * MAXN][26];
    int len[2 * MAXN];
    int lnk[2 * MAXN];
    int cnt[2 * MAXN];

    int siz;
    int lst;

    void Init() {
        siz = 1;
        lst = 1;
        ms(nxt[1]);
        len[1] = 0;
        lnk[1] = 0;
        cnt[1] = 0;
    }

    void Extend(int c) {
        int u = ++siz, p = lst;
        lst = u;
        len[u] = len[p] + 1;
        cnt[u] = 1;
        while(p != 0 && nxt[p][c] == 0) {
            nxt[p][c] = u;
            p = lnk[p];
        }
        if(p == 0) {
            lnk[u] = 1;
            return;
        }
        int q = nxt[p][c];
        if(len[p] + 1 == len[q]) {
            lnk[u] = q;
            return;
        }
        int v = ++siz;
        mc(nxt[v], nxt[q]);
        len[v] = len[p] + 1;
        lnk[v] = lnk[q];
        cnt[v] = 0;
        while(p != 0 && nxt[p][c] == q) {
            nxt[p][c] = v;
            p = lnk[p];
        }
        lnk[q] = v;
        lnk[u] = v;
    }

    int c[2 * MAXN];
    int a[2 * MAXN];

    void Calc() {
        ll ans = 0;
        for(int i = 1; i <= siz; ++i)
            c[i] = 0;
        for(int i = 1; i <= siz; ++i)
            ++c[len[i]];
        for(int i = 1; i <= siz; ++i)
            c[i] += c[i - 1];
        for(int i = 1; i <= siz; ++i)
            a[c[len[i]]--] = i;
        for(int i = siz; i >= 1; --i) {
            int u = a[i];
            cnt[lnk[u]] += cnt[u];
            ll tmp = 1LL * cnt[u] * len[u];
            if(cnt[u] > 1)
                cmax(ans, tmp);
        }
        printf("%lld
", ans);
    }

} sam;

有唯一的一个初始状态,有一个或多个终止状态。
从初始状态开始的任意一条由nxt组成的路径,都是原串的一个子串;原串的每一个子串都对应一条这样的路径。
由初始状态开始的到终止状态的任意一条由nxt组成的路径,都是原串的一个后缀;原串的每一个后缀都对应这样的一条路径。

也就是子串对应(从初始状态开始的)路径,状态对应终止于这个状态的路径的集合。
endpos(t)字符串s的子串t在s的后缀自动机上对应的所有终止位置(状态)的集合。

两个非空子串(u,v),设|u|<=|v|,假如拥有相同的endpos,当且仅当u是v的后缀,且每一次出现u都是以v的后缀形式存在的。
两个非空子串,要么endpos毫不相交,要么其中一个的endpos是另一个的子集(在这种情况下,大的endpos集合对应的子串是小的endpos集合对应的子串的后缀)。

每个endpos集合对应一类子串。

后缀连接lnk组成一棵树,满足endpos(v)是endpos(lnk(v))的子集。

本质不同的子串的数量:
每个状态对应的(本质不同的)子串数量是 len(v),每次去掉前面的一个字符让len-1就得到了一个新的串。注意这个状态的净贡献是len[u]-len[lnk[u]] ,也就是减去被lnk父亲贡献过的部分。

也可以不使用lnk,使用nxt进行动态规划,使用nxt时,后缀自动机是一个DAG,直接写一个记忆化搜索完事。
dp[u]表示从状态u开始的路径的数量。
(dp[u]=1+sum dp[v])

本质不同的字符的总长度:
每个状态对应的(本质不同的)子串的总长度是 (len(v)+(len(v)+1))/2。也是要注意减去lnk父亲贡献过的部分。

也可以不使用lnk,使用nxt进行动态规划,使用nxt时,后缀自动机是一个DAG,直接写一个记忆化搜索完事。
dp1[u]表示从状态u开始的路径的数量,dp2[u]表示从状态u开始的路径的总长度,注意第二个转移表示这里面每一个串的长度加起来是dp2[v],其中每一个都变长了1,增加了dp1[v]。
(dp1[u]=1+sum dp1[v])
(dp2[u]=sum (dp2[v]+dp2[v]))

最小循环移位:
字符串S+S包含S的所有循环移位作为子串,对S+S构造后缀自动机,从初始状态开始贪心选最小的字符进行转移,然后转移恰好|S|次就行。

某个子串的出现次数:
找到这个子串对应的endpos集合,输出这个集合的大小。endpos集合不能显式维护,所以只能找这个集合的大小。假如一个节点不是复制创建的(也就是被插入创建的),则它的endpos大小为1,复制的状态和初始状态的endpos为0。然后cnt(lnk(v))+=cnt(v)把更长位置的出现次数叠加到短的状态上。(上述代码就是这个模板)

所有子串的出现次数:
在上面的“某个子串的出现次数”问题里面,一个状态实际上对应着 len[u]-len[lnk[u]] 个子串,这些子串拥有相同的贡献,他们的贡献都是 cnt[u] 。

字典序第k大的子串:
由于“子串对应从初始状态开始的路径”,那么实际上要找字典序第k大的路径。用动态规划求出从每个状态开始有多少种路径。
假如是求本质不同的,则使用

dp[u]表示从状态u开始的路径的数量。
(dp[u]=1+sum dp[v])

假如不是求本质不同的,则每一种节点开始并结束有cnt[u]种,也就是
(dp[u]=cnt[u]+(sum dp[v]))

    bool vis[2 * MAXN];
    ll dp[2 * MAXN];

    ll DP(int u) {
        if(vis[u])
            return dp[u];
        dp[u] = cnt[u];
        for(int i = 0; i < 26; ++i) {
            int v = nxt[u][i];
            if(v != 0)
                dp[u] += DP(v);
        }
        vis[u] = 1;
        return dp[u];
    }

    void Show(int u) {
        vector<pair<char, int> > res;
        for(int i = 0; i < 26; ++i) {
            int v = nxt[u][i];
            if(v != 0)
                res.eb('a' + i, v);
        }
//        printf("u=%d
", u);
//        printf("  lnk=%d
", lnk[u]);
//        printf("  cnt=%d
", cnt[u]);
//        printf("  dp=%lld
", dp[u]);
//        for(auto v : res)
//            printf("  v=%c %d
", v.first, v.second);
        for(auto v : res)
            Show(v.second);
        return;
    }

    void Calc2(int k) {
        for(int i = 1; i <= siz; ++i)
            vis[i] = 0;
        DP(1);
        int u = 1;
        while(k > 0) {
            for(int i = 0; i < 26; ++i) {
                int v = nxt[u][i];
                if(v != 0) {
                    if(k > dp[v])
                        k -= dp[v];
                    else {
                        putchar('a' + i);
                        k -= cnt[v];
                        u = v;
                        break;
                    }
                }
            }
        }
        putchar('
');
    }
原文地址:https://www.cnblogs.com/purinliang/p/14116059.html