zoukankan      html  css  js  c++  java
  • 后缀数组

    前言

    本高一蒟蒻实在不会后缀数组,又溜回来学啦~(话说这是第几次了?)

    感觉理解能力日益下降,一个小时才看懂板子(大雾)

    后缀数组是个神奇的玩意,具体怎么用我也不知道。

    本渣不会DC3,所以只讲倍增。

    基本定义

    子串:在字符串s中任取$i<=j$,从第i个字符到第j个字符形成的字符串即为s的子串,记上面这玩意为$s[i,j]$。

    后缀:从字符串某个位置i到字符串末尾形成的子串,即$s[i,len](1<=i<=len)$,记以i为开头的后缀为$suf(i)$。

    后缀数组(sa):表示排名为i的后缀的起始位置。

    排名数组(rk):表示起始位置为i的后缀的排名。

    开始搞事qwq~

    理解了上面这些定义之后,我们就可以乱搞了。直接快排$O(n^2logn)$多舒服。

    先丢个图~(然后本蒟蒻就自己写了个$O(nlog^2n)$的垃圾算法)

    实际上上面这张图完成第三次排序时就可以停止了。

    一开始,我们可以根据每一位字符的ascii码得到当前情况下该位字符的排名$x_i$。(排名当然可以重复啦~)

    然后第k次排序,对于第i位,我们将$x_i$作为第一关键字,$x_{i+2^{k-1}}$作为第二关键字。(显然的,当$i+2^{k-1}>n$时,第i位的第二关键字为0)

    我们来考虑一下这为什么是可行的:

    初始情况下,每一位所表示的字符串为以当前位为开头长度为1的字符串。

    第k(k>1)次排序中,每一位所表示的字符串为以当前位为开头长度为$2^k$的字符串。比较两个关键字,相当于先比较前$2^{k-1}$个字符,再比较后$2^{k-1}$个字符。然后更新x数组。

    因此,经过$logn$次的排序后,每一位所表示的字符串即以当前位为开头的后缀。x数组即为该字符串对应的rk数组。

    对于每次排序,使用快速排序可以获得$O(nlog^2n)$的优秀算法(大雾)

    然而有些聪(sang)明(xin)睿(bing)智(kuang)的出题人是会卡的,我们来想一下如何得到更优的时间复杂度。

    我们观察发现(显然的),只有两个关键字,且关键字大小是$O(n)$的,因此我们对关键字进行基数排序即可,时间复杂度$O(nlogn)$。(基础知识,自行百度)

    代码

    我们定义$x_i$为第i位的关键字,$y_i$为第二关键字排名第i的位置,m为当前关键字种类数(初始值设为128即可),c为桶。

    一开始先对每一位求出x值(第1次排序),对c做前缀和可以知道第一关键字为i时,排名最大可以是多少:

    1 for(int i=1;i<=n;i++)
    2     c[x[i]=s[i]]++;
    3 for(int i=2;i<=m;i++)
    4     c[i]+=c[i-1];
    5 for(int i=n;i;i--)
    6     sa[c[x[i]]--]=i;

    第k次排序时,第$n-2^{k-1}+1$位到第n位的第二关键字为0,显然第二关键字排名最前:

    1 for(int i=n-k+1;i<=n;i++)
    2     y[++num]=i;

    若$sa[i]>2^{k-1}$,表明第$sa[i]$位可作为第$sa[i]-2^{k-1}$位的第二关键字:

    1 for(int i=1;i<=n;i++)
    2     if(sa[i]>k)
    3         y[++num]=sa[i]-k;

    清空桶,将$x_i$加入桶中,求前缀和:

    1 for(int i=1;i<=m;i++)
    2     c[i]=0;
    3 for(int i=1;i<=n;i++)
    4     c[x[i]]++;
    5 for(int i=1;i<=m;i++)
    6     c[i]+=c[i-1];

    第二关键字越大,在第一关键字相同的桶中排名越大:

    1 for(int i=n;i;i--)
    2     sa[c[x[y[i]]]--]=y[i];

    以上便完成一次基数排序。该次排序的sa数组已完成,可以直接循环求出新的x数组。

    这里用y数组保存x数组,因为要用来计算新的x数组:

    1 swap(x,y);x[sa[num=1]]=1;
    2 for(int i=2;i<=n;i++)
    3     x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;

    后缀数组end.(后缀数组代码20行以内是真的哦~只要你缩行

    完整代码如下:

     1 int n,c[100010]={0},x[200010],y[200010],sa[200010],rk[200010];
     2 char s[100010];
     3 inline void suffix_array(int m){
     4     for(int i=1;i<=n;i++)
     5         c[x[i]=s[i]]++;
     6     for(int i=2;i<=m;i++)
     7         c[i]+=c[i-1];
     8     for(int i=n;i;i--)
     9         sa[c[x[i]]--]=i;
    10     for(int k=1;k<=n;k<<=1){
    11         int num=0;
    12         for(int i=n-k+1;i<=n;i++)
    13             y[++num]=i;
    14         for(int i=1;i<=n;i++)
    15             if(sa[i]>k)
    16                 y[++num]=sa[i]-k;
    17         for(int i=1;i<=m;i++)
    18             c[i]=0;
    19         for(int i=1;i<=n;i++)
    20             c[x[i]]++;
    21         for(int i=1;i<=m;i++)
    22             c[i]+=c[i-1];
    23         for(int i=n;i;i--)
    24             sa[c[x[y[i]]]--]=y[i];
    25         swap(x,y);x[sa[num=1]]=1;
    26         for(int i=2;i<=n;i++)
    27             x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
    28         if((m=num)==n)
    29             break;
    30     }
    31     for(int i=1;i<=n;i++)
    32         rk[sa[i]]=i;
    33     return;
    34 }

    height数组

    待更…(可能会咕~)

  • 相关阅读:
    Apache ActiveMQ 远程代码执行漏洞 (CVE-2016-3088)案例分析
    linux 软中断过高性能优化案例
    jvm默认的并行垃圾回收器和G1垃圾回收器性能对比
    JVM性能参数优化
    一次压测中tomcat生成session释放不及时导致的频繁fullgc性能优化案例
    sed命令实现文件内容替换总结案例
    You have new mail in /var/spool/mail/root消除提示的方法
    zookeeper常用命令
    mongodb输错命令后不能删除问题
    centos环境gcc版本升级
  • 原文地址:https://www.cnblogs.com/gzez181027/p/suffix_array.html
Copyright © 2011-2022 走看看