后缀数组 学习笔记

后缀数组 学习笔记

定义

我们定义后缀数组 Sa 中的元素 Sai 为,字典序排名为 i 的后缀所在的位置。我们定义排名数组 Rank 中的元素 Ranki 为,在位置 i 的后缀的排名。

求解后缀数组

首先 O(n2logn) 的解法很显然,直接排序。

那么有一个利用倍增的思想求解后缀数组的 O(nlog2n)的解法 ,首先,我们优先对单个字符排序,发现出现重复的话,我们倍增扩大长度,再次进行比较,直到每个后缀的排名各不相同。

其实还有 O(nlogn)O(n) 的解法,详见 后缀数组简介 - OI Wiki

Code

/*
 * @Author: Ehundategh
 * @Date: 2023-10-22 20:15:56
 * @FilePath: \Code\Model\String\SA.cpp
 * @Description: You Steal,I kill
 */
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 2000010
using namespace std;
int Suffix_Array[MAXN],Rank[MAXN],n,k,Height[MAXN];
int Temp[MAXN];
char In[MAXN];
bool cmpsa(int i,int j){
    if(Rank[i]!=Rank[j]){
        return Rank[i]<Rank[j];
    }
    else{
       int Secondi=i+k<=n?Rank[i+k]:-1;
       int Secondj=j+k<=n?Rank[j+k]:-1;
       return Secondi<Secondj;
    }
}
void Get_SA(){
    for(int i=1;i<=n;i++){
        Suffix_Array[i]=i; Rank[i]=In[i];
    }
    for(k=1;k<=n;k*=2){
        sort(Suffix_Array+1,Suffix_Array+n+1,cmpsa);
        Temp[Suffix_Array[1]]=0;
        for(int i=1;i<=n;i++){
            Temp[Suffix_Array[i+1]]=Temp[Suffix_Array[i]]+(cmpsa(Suffix_Array[i],Suffix_Array[i+1])?1:0);
        }
        for(int i=1;i<=n;i++) Rank[i]=Temp[i];
    }
    return;
}
int main(){
    scanf("%s",In+1);
    n=strlen(In+1);
    Get_SA();
    for(int i=1;i<=n;i++) printf("%d ",Suffix_Array[i]);
}

这样就可以求出后缀数组了。

例题:

[[JSOI 2007]字符加密]([P4051 JSOI2007] 字符加密 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

思路
首先我们先把字符串复制一遍,那么将全新的字符串的后缀排序,从后缀排名从小到大遍历,如果出现后缀位置在原串中,就输出当前位置前一个位置的字符

高度数组

高度数组,后缀数组的神。

分为以下三个部分讲解:

  • 定义
  • 求解
  • 例题

定义

首先我们先定义 LCP(S1,S2)(longest Common Prefix) 即最长公共前缀,为:一个最大的 x 满足 S1i=S2i(1ix)。而我们下面提到的 LCP(a,b),都是表示以 (a,b) 两个位置为起点的后缀的最长公共前缀。

我们定义:Heighti=LCP(Sai,Sai1),特别地, Height1=0

举个例子,方便理解高度数组。

我们来看以下的字符串:abcbc,对于这一个字符串,我们尝试求解其高度数组。

首先有 Height1=0

然后我们来求解 Height2,将 Sa2Sa1 作比较求最长公共前缀,也就是求解 abcbcbc 的最长公共前缀,也就是 0

然后对于 Height3,将 Sa3Sa2 作比较求最长公共前缀,那么比较 bcbcbc 即可,答案为 2

对于 Height4Height5 同理可得答案为,0,1

求解

求解有一个神奇的引理 HeightRankiHeightRanki11

关于这个引理的证明。详见2009国家队论文。

那么我们就可以通过这个引理,来实现 O(n) 时间复杂度的高度数组处理。

每次尝试与排名上一个的后缀比较,拓展得到当前的 Heighti 即可,而拓展也不用从 0 开始,从上次的 Heighti1 开始即可,这样高度数组最多变化 3n 次,所以时间复杂度是 O(n)

代码

void Get_Height(){
    for(int i=1,k=0;i<=n;i++){
        if(Rank[i]==0)continue;
        if(k>0) --k;
        while(In[i+k]==In[Suffix_Array[Rank[i]-1]+k])++k;
        Height[Rank[i]]=k;
    }
}

例题

ADASTRNG - Ada and Substring

思路
高度数组模板题,对于每一个字符开头的后缀只需要每次统计总共的子串个数,然后再减去两两后缀的最长公共前缀去重即可。

UVA Editor

思路
对于每一组询问,求Height的max即可。(话说这道题有一个加强版,要求出现k次,但是其实是一个做法,只需要求相邻k-1个的Height的max即可)

posted @   Ehundategh  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示