后缀数组简介(洛谷P3809)

推荐这篇Blog(写的比我详细多了Orz)

算法用途

在许多场合可以替代后缀树,也可以和其他算法结合搞许多事情,是一个很强大的东东。

各种东西

求后缀数组的整个过程其实就是基数排序,所以建议先学会基数排序。

① 后缀:即对于字符串 s s s[i]s[n]即为s的一个后缀( i[1,n] i ∈ [ 1 , n ] )。
rank r a n k 数组: rank[i] r a n k [ i ] 表示以 i i 开头的后缀的排名(排名按字典序)。
SA数组:即后缀数组,表示排名为 i i 的后缀的开头在原字符串中的位置。

算法实现

暴力的求法就是把所有后缀存起来排一遍序,但是当strlen(s)太大时(比如1e6)空间就受不了,而且字符串是有长度的,因此时间复杂度为 O(nlog2n2) O ( n ∗ l o g 2 n 2 ) ,也是不能够接受的。所以我们需要另寻出路。

不难发现 SA S A 数组与 rank r a n k 数组互为逆运算。
所以只要求出其中一个,另一个就出来了。

而求 rank r a n k 我们可以通过倍增实现。以目前的 rank r a n k 为第一关键字,以之前的 rank r a n k 为第二关键字进行基数排序。

时间复杂度 O(nlog2n) O ( n l o g 2 n ) (常数巨大)

给一张经典图自行体会(我好懒啊)
这里写图片描述

模板(代码解释)

洛谷P3809为例:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 1000000
using namespace std;
int n,m;
int sa[MAXN+5],t[MAXN+5],rk[MAXN+5],rs[MAXN+5];
char s[MAXN+5];
void Rs(){//基数排序
    memset(rs,0,sizeof(rs));//先把桶清零
    for (int i=1;i<=n;i++)  rs[rk[t[i]]]++;//放入桶里
    for (int i=1;i<=m;i++)  rs[i]+=rs[i-1];//累加得到排名(未离散)
    for (int i=n;i>=1;i--)  sa[rs[rk[t[i]]]--]=t[i];//更新至t数组中(t数组为临时数组)
}
void SA(){//构造SA数组
    m=122; n=strlen(s+1);//题目所给的字符串中最大的字符ascll码为122('z')
    for (int i=1;i<=n;i++){ rk[i]=s[i]; t[i]=i; }//初始化时rk直接取ascll码即可
    Rs();
    for (int k=1;k<=n;k*=2){
        int p=0;
        for (int i=n-k+1;i<=n;i++)  t[++p]=i;//先把第二关键字为0的放进去
        for (int i=1;i<=n;i++)  if (sa[i]>k) t[++p]=sa[i]-k;//如果sa[i]>k那么sa[i]-k也可以作为第二关键字
        Rs();   memcpy(t,rk,sizeof(t));//排序后copy一份
        p=1; rk[sa[1]]=1;
        for (int i=2;i<=n;i++)
            if (t[sa[i]]==t[sa[i-1]]&&t[sa[i]+k]==t[sa[i-1]+k]) rk[sa[i]]=p;
            //如果不并列新增一个排名
            else rk[sa[i]]=++p;
        if (p==n) break;//如果没有并列的表明已经排完
        m=p;
    }
}
int main(){
    scanf("%s",s+1);
    SA();
    for (int i=1;i<=n;i++)  printf("%d ",sa[i]);
    return 0;
}

LCP相关

定义

LCP:最长公共前缀。

H[i]:sa[i]和sa[i-1]的LCP,即排名相近的两个后缀的最长公共前缀。

h[i]:H[rk[i]],代到前面就可以发现就是原串中两个相邻后缀的LCP。

那么sa[i]和sa[j]的前缀就是min(H[i+1]~H[j]),这个可以通过ST表预处理。

实现

对于h数组有这个性质:h[i]>=h[i-1]-1。这就意味着我们可以 O(n) O ( n ) 的求出h数组。

至于这个性质么。。。YY一下应该挺显然吧。 因为我太菜了不会证

而根据h[i]=H[rk[i]],就可以求出H数组了。

贴一下代码:

void mkh(){
    for (int i=1,k=0;i<=n;i++){
        if (k) k--;
        while (s[i+k]==s[sa[rk[i]-1]+k]) k++;
        H[rk[i]]=k;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值