zoukankan      html  css  js  c++  java
  • 2019年华南理工大学程序设计竞赛(春季赛) K Parco_Love_String(后缀自动机)找两个串的相同字串有多少

    https://ac.nowcoder.com/acm/contest/625/K

    题意:

    给出Q 个询问 i , 求 s[0..i-1] 与 s[i...len-1] 有多少相同的字串

    分析:

    给出了查询 , 容易想到先预处理出答案好吧 , 字符串的问题也容易想到后缀自动机 ,但是我们该怎么使用呢?

    下面提供我的思路;

    我们建立出SAM后 , 跑一边拓扑排序 ,根据SAM跑出来的拓扑排序的序列特性 , 我们可以求出 在当前状态st 的最大串字符出现的个数

            for (int i = now; i >= 1; --i) {///得到的是最大字符串的出现次数
                int x = rank[i];
                endpos[slink[x]] += endpos[x];
            }

    可是这次我们需要求的是相同的串有多少 , 我们不可以暴力出SAM里面存有的串的个数 , 现在就来搞一个很奇妙的东西,

    我们可以根据上面求出来的endpos , 去推出 有多少相同的字符串;

            for(int i=1 ; i<=now; i++)///得到全部串的出现次数
            {
                int x=rank[i]; ///到x这个状态时 , 有多少的后缀总共串
                sum[x] = sum[slink[x]] + endpos[x]*(maxlen[x] - maxlen[slink[x]]);
                //cout<<sum[x]<<endl;
            }

    我们知道 对于 now1 , 与now2=slink[now1] , 如果now1状态出现了 , 那么now2 的状态也肯定会出现 , 因为silnk 是链接now1接下去的后缀 ,也就是说now2 是now1的后缀

    所以我们求当前now 有多少串相同的时候 , 就要+上一个的后缀价值 sum[x] = sum[slink[x]】 + 当前的价值

    当前的价值又是 怎么计算呢?

    我们知道 maxlen[x] - maxlen[slink[x]]  是表示当前的状态x 里面有多少的串 , 那这个状态出现的次数与包含的串相乘 , 不就是当前我们需要求的价值了吗

    上面可能说的比较乱 , 主要是我巨菜不知如何表达鸭

    上面是用str1 串去构建的SAM , 然后用str2 在这个自动机里面跑 , 与求LCA 很相似

    可以参考https://www.cnblogs.com/shuaihui520/p/10686862.html

    #include <bits/stdc++.h>
    #define LL long long
    #define P pair<int, int>
    #define lowbit(x) (x & -x)
    #define mem(a, b) memset(a, b, sizeof(a))
    #define rep(i, a, n) for (int i = a; i <= n; ++i)
    #define mid ((l + r) >> 1)
    #define lc rt<<1
    #define rc rt<<1|1
    #define ll long long
    using namespace std;
    const int maxn = 1005;
    struct SAM{
    
        int trans[maxn<<1][26], slink[maxn<<1], maxlen[maxn<<1];
        // 用来求endpos
        int indegree[maxn<<1], endpos[maxn<<1], rank[maxn<<1], ans[maxn<<1];
        // 计算所有子串的和(0-9表示)
        LL sum[maxn<<1],D[maxn];
        int last, now, root;
    
        inline void newnode (int v) {
            maxlen[++now] = v;
            mem(trans[now],0);
        }
    
        inline void extend(int c) {
            newnode(maxlen[last] + 1);
            int p = last, np = now;
            // 更新trans
            while (p && !trans[p][c]) {
                trans[p][c] = np;
                p = slink[p];
            }
            if (!p) slink[np] = root;
            else {
                int q = trans[p][c];
                if (maxlen[p] + 1 != maxlen[q]) {
                    // 将q点拆出nq,使得maxlen[p] + 1 == maxlen[q]
                    newnode(maxlen[p] + 1);
                    int nq = now;
                    memcpy(trans[nq], trans[q], sizeof(trans[q]));
                    slink[nq] = slink[q];
                    slink[q] = slink[np] = nq;
                    while (p && trans[p][c] == q) {
                        trans[p][c] = nq;
                        p = slink[p];
                    }
                }else slink[np] = q;
            }
            last = np;
            // 初始状态为可接受状态
            endpos[np] = 1;
        }
    
    
        inline void init()
        {
            root = last = now = 1;
            slink[root]=0;
            mem(trans[root],0);
            mem(endpos,0);
            mem(sum,0);
            mem(indegree,0);
            mem(rank,0);
        }
    
        inline void getEndpos() {
            // topsort
            for (int i = 1; i <= now; ++i) indegree[ maxlen[i] ]++; // 统计相同度数的节点的个数
            for (int i = 1; i <= now; ++i) indegree[i] += indegree[i-1];  // 统计度数小于等于 i 的节点的总数
            for (int i = 1; i <= now; ++i) rank[ indegree[ maxlen[i] ]-- ] = i;  // 为每个节点编号,节点度数越大编号越靠后
            // 从下往上按照slik更新
            for (int i = now; i >= 1; --i) {///得到的是最大字符串的出现次数
                int x = rank[i];
                endpos[slink[x]] += endpos[x];
            }
            for(int i=1 ; i<=now; i++)///得到全部串的出现次数
            {
                int x=rank[i]; ///到x这个状态时 , 有多少的后缀总共串
                sum[x] = sum[slink[x]] + endpos[x]*(maxlen[x] - maxlen[slink[x]]);
                //cout<<sum[x]<<endl;
            }
        }
        ///用一个串去跑的自动机
        inline void work(string s,int W)
        {
            getEndpos();
            int len=s.size();
            int now=root;
            int t1=0;
            ll ret=0;
            for(int i=0 ; i<len ; i++)
            {
                int nowid=s[i]-'a';
                if(trans[now][nowid])///这个状态有了 , 去下一个状态找
                {
                    t1++;
                    now=trans[now][nowid];
                    //ret+=sum[slink[now]] + endpos[now]*(t1-maxlen[slink[now]]);
                }
                else
                {   while(now!=0 && trans[now][nowid]==0) {now=slink[now];}///缩小范围找满足条件的
    
                    if(now)
                    {
                        t1 = maxlen[now]+1;
                        now=trans[now][nowid];
    
                    }
                    else
                    {
                        t1=0;now=root;
                    }
                }
                ret+=sum[slink[now]] + endpos[now]*(t1-maxlen[slink[now]]);
            }
            D[W]=ret;
            //return ret;
        }
    
    
    }sam;
    
    int main()
    {
    
    
        string T;cin>>T;
        int len=T.size();
    
        for(int i=1 ; i<len ; i++)
        {
            string t2;
            sam.init();
            for(int j=0 ; j<i ; j++)
            {
                sam.extend(T[j]-'a');
            }
    
            for(int j=i ; j<len ; j++)
            {
                t2+=T[j];
            }
            sam.work(t2,i);
    
        }
        int E;scanf("%d",&E);
        while(E--)
        {
            int x;
            scanf("%d",&x);
            printf("%lld
    ",sam.D[x]);
        }
    
    
       //- sam.all();
    }
    View Code
  • 相关阅读:
    OSI安全体系结构
    PHP 二维数组根据相同的值进行合并
    Java实现 LeetCode 17 电话号码的字母组合
    Java实现 LeetCode 16 最接近的三数之和
    Java实现 LeetCode 16 最接近的三数之和
    Java实现 LeetCode 16 最接近的三数之和
    Java实现 LeetCode 15 三数之和
    Java实现 LeetCode 15 三数之和
    Java实现 LeetCode 15 三数之和
    Java实现 LeetCode 14 最长公共前缀
  • 原文地址:https://www.cnblogs.com/shuaihui520/p/10713336.html
Copyright © 2011-2022 走看看