zoukankan      html  css  js  c++  java
  • 【字符串】后缀自动机

    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('
    ');
        }
    
  • 相关阅读:
    Sublime Text 2 && MinGW G++ On Windows
    [zz]linux IO(function open read write close)
    PHP的基本常识(1)
    helloworld.c 的一次系统旅行(1) 读书笔记
    PHP 仿博客园 个人博客(1)
    配置 .htaccess 单点入口
    仿博客园个人博客(3)基本完成
    PHP的基本常识(2)
    JQuery 获得所有表单值
    PHP 仿博客园 个人博客(2)
  • 原文地址:https://www.cnblogs.com/purinliang/p/14116059.html
Copyright © 2011-2022 走看看