• 后缀数组初探


    前言:JSOI2017round2day2T1后缀数组模板题一点都没看出来。不然就翻盘了

      痛定思痛后决定恶补字符串。

    后缀数组

    全是论文上抄来的。

    学了一周,终于搞懂一些了。

    1.基本定义

      字符串S;

      后缀:从位置i开始到结尾的子串,记作Suffix(i)=s[i..len[s]];

      后缀数组sa[1..n]

      表示1..n的一个排列,满足Suffix(sa[i])<Suffix(sa[i-1])

      即:在所有后缀中排第i的是谁

      名次数组rank[1..n]

      表示Suffix(i)在后缀中的名次。

      即:后缀i排第几。

      所以sa与rank为逆运算。

    2.倍增算法求后缀数组。

      //学过DC3邪教的自行离开。其实我是用后缀自动机构造的。

      算法非常好理解,就是用倍增求出每个字符开始长度为2^k的子字符串排序

      k==0..log2n上取整, 最后一遍处理时,子串相当于后缀。

      代码:

     1 void suffix(){
     2     register int i,k;
     3     memset(cnt,0,sizeof(cnt));
     4     for (i=1;i<=l;i++)    cnt[s[i]]++;
     5     for (i=1;i<=MAXASCLL;i++)    cnt[i]+=cnt[i-1];
     6     for (i=l;i>0;i--) sa[cnt[s[i]]--]=i;
     7     rank[sa[1]]=1;
     8     for (i=2;i<=l;i++)    rank[sa[i]]=rank[sa[i-1]]+(s[sa[i]]!=s[sa[i-1]]);
     9     for (k=1;rank[sa[l]]<l;k*=2){
    10         for (i=1;i<=l;i++) x[i]=rank[i];
    11         for (i=1;i<=l;i++) y[i]=(i+k<=l)?rank[i+k]:0;//x为第一关键字,y为第二关键字
    12         memset(cnt,0,sizeof(cnt));
    13         for (i=1;i<=l;i++) cnt[y[i]]++;
    14         for (i=1;i<=l;i++) cnt[i]+=cnt[i-1];
    15         for (i=l;i>0;i--) say[cnt[y[i]]--]=i;
    16         memset(cnt,0,sizeof(cnt));
    17         for (i=1;i<=l;i++) cnt[x[i]]++;
    18         for (i=1;i<=l;i++) cnt[i]+=cnt[i-1];
    19         for (i=l;i>0;i--) sa[cnt[x[say[i]]]--]=say[i];//基数排序,先排y,再排x
    20         rank[sa[1]]=1;
    21         for (i=2;i<=l;i++) rank[sa[i]]=rank[sa[i-1]]+(x[sa[i]]!=x[sa[i-1]]||y[sa[i]]!=y[sa[i-1]]);    
    22     }
    23 }
    View Code

     这里有个小优化:若rank[sa[n]]已经为n时(所有串已经分出大小),就退出。

    时间复杂度:O(n log n);不要尝试跑1e6的构造数据(字符基本相同),常数巨大QAQ

    3.height数组(最长公共前缀lcp)

      学会了构造,就要用后缀数组搞事情了。

      最经典的运用就是height数组。

      定义height[i]=Suffix(sa[i])与Suffix(sa[i-1])的最长公共前缀。

      对于Suffix(j),Suffix(k); rank[j]<rank[k];

      他们的最长公共前缀为min(height[rank[j]+1]..height[rank[k]]);

      毛想想,由于序列是升序的,那么数组的变换是不可逆(若sa[i],sa[i+1]的第j位发生了变化,那么sa[i],sa[i+1]的第j位(或以前的位)一定也变了)。

      那么问题就是如何快速求出height数组了。

      定义h[i]=height[rank[i]] Suffix(i)与前一名的最长公共前缀。

      举个例子:

      S=abaac

      Suffix=abaac, baac, aac, ac, c

      sa=3,1,4,2,5

      rank=2,4,1,3,5

      height=0,1,1,0,0

      h=1,0,0,1,0

      h数组有以下性质: h[i]>=h[i-1]+1;

      证明: 设Suffix(k)是排在Suffix(i-1)前一名的后缀(rank[k]=rank[i-1]-1)

      则它们的最长公共前缀为h[i-1],

      若h[i-1]<2, 那么显然成立,当h[i-1]>=2时,S[i-1..i]==S[k..k+1],

      且应为k在i-1的前一位,所以k+1排在i的前一位。

      所以至少要共享后h[i-1]-1位

    void makeheight(){
        register int i,k=0,j;
        for (i=1;i<=l;i++){
            k=(k>0)?k-1:0;
            if (rank[i]==1){
                height[rank[i]]=0; k=0;
                continue;
            }
            for (j=sa[rank[i]-1];(j+k<=l)&&(i+k<=l)&&(s[j+k]==s[i+k]);k++);
            height[rank[i]]=k;
        }
    }
    View Code

      有了height数组,就可以用rmq求出lcp

      例题:[poj2774]求两个串的最长公共子串(l<100000)

      解法:将两串相连,在中间加一个没用到的字符,求出height数组,在其中找一个最大的height,且所指的两个串一个在空字符前一个在空字符后。

  • 相关阅读:
    Jobs(一)前端页面
    MySQL 聚合函数与count()函数
    计算机概论 64bit和32bit的CPU的不同
    Maven如何发布项目到一个Tomcat中
    创建一个简单的Maven工程
    Maven安装与配置
    Maven整合Spring与Solr
    solr应用
    hashCode()与equals()区别
    Callable与Future
  • 原文地址:https://www.cnblogs.com/Vanisher/p/6880454.html
Copyright © 2020-2023  润新知