• 后缀数组/后缀自动机 入门转载&板子


    HihoCoder题库中搜索后缀,就有关于后缀数组以及后缀自动机的入门题目以及讲解。

    后缀数组

    五分钟搞懂后缀数组!后缀数组解析以及应用(附详解代码)

    后缀数组 最详细讲解

    int sa[N],ra[N],height[N];
    int A[N],B[N],cntA[N],cntB[N],tsa[N];
    //sa[i]按字典序排序后,第i个后缀在原串中的位置
    //ra[i]原串中i~n的后缀,按字典序排序后的编号
    //height[i] 按字典序排序后,第i个后缀与第i-1个后缀的最长公共前缀
    //A,B进行基数排序的两个关键字,cntA,cntB进行基数排序的“桶”,
    //tsa[i]临时数组,按第二个关键字排序后的串 
    deque<int> q;
    void getsa(int n){
        //一开始以串中单个单位作为第一关键字排序 
        for(int i=0;i<=100;i++) cntA[i]=0;
        for(int i=1;i<=n;i++) cntA[a[i]]++;
        for(int i=1;i<=n;i++) cntA[i]+=cntA[i-1];
        for(int i=n;i>=1;i--) sa[cntA[a[i]]--]=i;
        ra[sa[1]]=1;
        for(int i=2;i<=n;i++){
            ra[sa[i]]=ra[sa[i-1]];
            if(a[sa[i]]!=a[sa[i-1]]) ra[sa[i]]++;
        }
        //倍增的思想
        for(int l=1;ra[sa[n]]<n;l<<=1){
            for(int i=0;i<=n;i++) cntA[i]=cntB[i]=0;
            //ra[i]作为第一关键字,ra[i+l]作为第二关键字 
            for(int i=1;i<=n;i++){
                cntA[A[i]=ra[i]]++;
                cntB[B[i]=(i+l<=n ? ra[i+l] : 0)]++;
            } 
            for(int i=1;i<=n;i++){
                cntA[i]+=cntA[i-1];
                cntB[i]+=cntB[i-1];
            }
            for(int i=n;i>=1;i--) tsa[cntB[B[i]]--]=i;
            for(int i=n;i>=1;i--) sa[cntA[A[tsa[i]]]--]=tsa[i];
            ra[sa[1]]=1;
            for(int i=2;i<=n;i++){
                ra[sa[i]]=ra[sa[i-1]];
                if(A[sa[i]]!=A[sa[i-1]]||B[sa[i]]!=B[sa[i-1]]) ra[sa[i]]++;
            }
        }
        //height[ra[i]]>=height[ra[i-1]]-1
        for(int i=1,j=0;i<=n;i++){
            if(j) j--;
            while(a[i+j]==a[sa[ra[i]-1]+j]) j++;
            height[ra[i]]=j;
        }
        return ;
    }
    板子1
    int a[N];
    int sa[N],ra[N],height[N];
    int cntr[N],tsa[N];
    //cntr"桶",tsa[i]临时数组,不同情况意义不同 
    deque<int> q;
    //根据第一关键字ra[i]和以及按第二关键字[i+l]排好序的tsa进行基数排序 
    void sasort(int n,int m){
        for(int i=0;i<=m;i++) cntr[i]=0;
        for(int i=1;i<=n;i++) cntr[ra[i]]++;
        for(int i=1;i<=m;i++) cntr[i]+=cntr[i-1];
        for(int i=n;i>=1;i--) sa[cntr[ra[tsa[i]]]--]=tsa[i];
    }
    void getsa(int n){
        int m=100;//初始的数据大小,比如ascii码的话,一般为127 
        for(int i=1;i<=n;i++) ra[i]=a[i],tsa[i]=i;
        sasort(n,m);
        for(int l=1;l<=n;l<<=1){
            int num=0;
            //[n-l+1,n]的数第二关键字为0,按第二关键字排序肯定是在前面 
            for(int i=n-l+1;i<=n;i++) tsa[++num]=i;
            // 如果sa[i]>l,那么它可以作为sa[i]-l的第二关键字 
            for(int i=1;i<=n;i++) if(sa[i]>l) tsa[++num]=sa[i]-l; 
            sasort(n,m);
            //重复空间利用,用tsa暂时存下上一轮的ra用于比较求出新的ra
            memcpy(tsa,ra,(n+1)*sizeof(int));
            ra[sa[1]]=1;
            for(int i=2;i<=n;i++){
                ra[sa[i]]=ra[sa[i-1]];
                if(tsa[sa[i]]!=tsa[sa[i-1]]||tsa[sa[i]+l]!=tsa[sa[i-1]+l]) ra[sa[i]]++;
            }
            m=ra[sa[n]];
            if(m>=n) break;
        }
        for(int i=1,j=0;i<=n;i++){
            if(j) j--;
            while(a[i+j]==a[sa[ra[i]-1]+j]) j++;
            height[ra[i]]=j;
        }
        return ;
    }
    板子2

    板子1为HihoCoder中的源码,而板子2为我自己根据网上的代码改写的。

    板子1相对比较稳定,而板子2更省空间,运行也快一点。

    后缀自动机

    后缀自动机(SAM)学习笔记

    int size,last,maxlen[N];//minlen[N];
    //拥有相同endpos集合的为同一状态
    //对于同一状态中的字符串,他们都是该状态最长子串的后缀 
    //size总状态数,last上一个状态编号,maxlen[i]:i状态包含的最长子串长度 
    int link[N],trans[N][31];
    //trans[i][j] 转移函数,为i状态遇到j字符会转移到哪个状态
    //link[i] SuffixLinks,i状态的连续后缀在哪个状态断开 
    void initsam(int n){
        size=last=1;
        for(int i=0;i<=n;i++){
            link[i]=maxlen[i]=0;//minlen[i]=0;
            for(int j=0;j<26;j++) trans[i][j]=0;
        }
    }
    void extend(int x){
        int cur=++size,u;
        maxlen[cur]=maxlen[last]+1;
        //Suffixpath(cur-S)路径上没有对x的转移的状态,添加到cur的转移 
        for(u=last;u&&!trans[u][x];u=link[u]) trans[u][x]=cur;
        //若Suffixpath(cur-S)路径上的状态都没有对x的转移,那么此时curlink到初状态即可 
        if(!u) link[cur]=1;
        else{
            //若Suffixpath(cur-S)路径存在有对x转移的状态u
            //而v是u遇到x后转移到的状态 
            int v=trans[u][x];
            //若v中最长的子串添加上x便是u的最长子串,此时将curlink到v 
            //也就是v状态中的子串都是cur状态中的后缀,且cur的后缀序列刚好在v处断开 
            if(maxlen[v]==maxlen[u]+1) link[cur]=v;
            else{
                //否则创建个中间状态进行转移 
                //也就是cur状态和v状态都有着部分相同的后缀,而之前这些后缀保存在v状态 
                //而v状态中还有些状态不是cur状态的后缀的,所以需要个新状态表示他们共有的后缀 
                int clone=++size;
                maxlen[clone]=maxlen[u]+1;
                memcpy(trans[clone],trans[v],sizeof(trans[v]));
                link[clone]=link[v];
            //    minlen[clone]=maxlen[link[clone]]+1;
                //原先添加x后转移到v的状态,现在都转移到中间状态 
                for(;u&&trans[u][x]==v;u=link[u]) trans[u][x]=clone;
                //最后,因为cur状态和v状态的后缀都在中间状态这里断开
                //所以cur和v都link到中间状态 
                link[cur]=link[v]=clone;
            //    minlen[v]=maxlen[link[v]]+1;
            }
        }
    //    minlen[cur]=maxlen[link[cur]]+1;
        last=cur;
        return ;
    }
    板子1
    struct Sam{
        int size,last,len[N],link[N],trans[N][31];
        Sam(){
            size=last=1;
        }
        void extend(int x){
            int cur=++size,u;
            len[cur]=len[last]+1;
            for(u=last;u&&!trans[u][x];u=link[u]) trans[u][x]=cur;
            if(!u) link[cur]=1;
            else{
                int v=trans[u][x];
                if(len[v]==len[u]+1) link[cur]=v;
                else{
                    int clone=++size;
                    len[clone]=len[u]+1;
                    link[clone]=link[v];
                    memcpy(trans[clone],trans[v],sizeof(trans[v]));
                    for(;u&&trans[u][x]==v;u=link[u]) trans[u][x]=clone;
                    link[cur]=link[v]=clone;
                }
            }
            last=cur;
        }
    }A;
    板子2

    板子2写成结构体形式,方便多个后缀自动机时调用。

    后缀数组与后缀自动机的比较

    转自【整理】如何选取后缀数组&&后缀自动机

    • 单个字符串问题                                                                  不等号是“优于”,&&是差不多(以下是个人感觉)

      • 1重复子串
        • 1,1 可交叉最长重复子串                              后缀自动机>=后缀数组                          都是基本题,但是前者代码稍短
        • 1,2 不可交叉最长重复子串                          后缀数组>=后缀自动机                          前者易于判断交叉;后者则需要记录每个状态所有出现的位置
        • 1,3 可交叉的k次最长重复子串                     后缀自动机>=后缀数组                          后者需+二分判定;前者无需判断,直接拓扑出每个状态的次数                     
      • 2子串个数问题
        • 2,1 不相同子串个数                                     后缀自动机&&后缀数组                          都是基本功能,易于实现。
      • 3循环子串问题
        • 3,1 求最小循环节                                         后缀数组,kmp                                           后缀自动机应该不行。
        • 3,2 重复次数最多的连续重复子串                后缀数组
    • 两个字符串串问题

      • 1公共子串问题 
        • 1,1 最长公共子串                                         后缀自动机&&后缀数组                          都是基本功能
      • 2子串个数问题
        • 2,1 特定长度的公共子串                              后缀自动机&&后缀数组                           二者的基本功能
    • 多个字符串问题

      • 1公共子串问题
        • 1,1 在k个字符串中的出现的最长子串           广义后缀自动机>=后缀数组(KMP也可以求多个串的最长公共字串)               (具体效率谁高取决于数据)
        • 1,2 在每个字符串中出现k次的最长子串       广义后缀自动机>=后缀数组
        • 1,3 在每个字符串中或反转后出现的最长子串 广义后缀自动机?后缀数组            
    • 其他

    • 最小表示法: 后缀自动机                
    • 最小循环节 :后缀数组
  • 相关阅读:
    jmeter中设置线程数与设置集合点的区别
    在linux系统中如何通过shell脚本批量设置redis键值对
    服务器带宽上行与下行的区别
    性能测试之Jmeter插件安装
    sqlserver 启用邮箱服务
    sqlserver 自定义字符串分割函数.
    C# 重写思想
    CSS控制鼠标滑过时的效果
    js实现图片自动切换效果。
    SQL Server Management Studio 使用作业实现数据库备份
  • 原文地址:https://www.cnblogs.com/LMCC1108/p/13330949.html
Copyright © 2020-2023  润新知