后缀数组入门(二)——Height数组与LCP

前言

看这篇博客前,先去了解一下后缀数组的基本操作吧:后缀数组入门(一)——后缀排序

这篇博客的内容,主要建立于后缀排序的基础之上,进一步研究一个\(Height\)数组以及如何求\(LCP\)。


什么是\(LCP\)

\(LCP\),即\(Longest\ Common\ Prefix\),是最长公共前缀的意思。

而在后缀数组中,\(LCP(i,j)\)表示后缀\(_{SA_i}\)与后缀\(_{SA_j}\)的最长公共前缀的长度,注意是\(SA_i\)和\(SA_j\),而不是\(i\)和\(j\)。


\(LCP\)的性质

先是几个比较简单的基本性质:

  • \(LCP(i,j)=LCP(j,i)\)

    这应该是比较显然的。

  • \(LCP(i,i)=n-SA_i+1\)

    这个性质非常重要,因为在求\(LCP\)的过程中要特判该情况,不然会死得特别惨

接下来,是一些比较复杂的性质:

  • \(LCP(i,j)=min(LCP(i,k),LCP(j,k))\)(对于任意\(1\le i\le k\le j\))

    首先,设\(x=min(LCP(i,k),LCP(j,k))\),则可得\(LCP(i,k)\ge x,LCP(j,k)\ge x\)。

    因此我们可以知道后缀\(_{SA_i}\),后缀\(_{SA_j}\)的前\(x\)个字符分别与后缀\(_{SA_k}\)的前\(x\)个字符相等

    后缀\(_{SA_i}\),后缀\(_{SA_j}\)的前\(x\)个字符相等,即\(LCP(i,j)\ge x\)。

    而由于后缀\(_{SA_i}<\)后缀\(_{SA_k}<\)后缀\(_{SA_{j}}\),且由\(x=min(LCP(i,k),LCP(j,k))\)可得,\(LCP(i,j)\le x\)。

    故\(LCP(i,j)=x\)。

  • \(LCP(i,j)=min_{k=i}^{j-1}LCP(k,k+1)\)

    由\(LCP(i,j)=min(LCP(i,k),LCP(j,k))\)这个性质,我们可以把\(LCP(i,j)\)拆成\(j-i\)个部分,分别为\(LCP(i,i+1),LCP(i+1,i+2),...,LCP(j-1,j)\)。

    然后再取\(min\)即可。

这两个性质虽然看似令人匪夷所思,但仔细理解其实还是能看懂的。

这两个性质在\(LCP\)的求解过程中发挥着十分重要的作用。


\(Height\)数组

为了方便求解\(LCP\),我们需要在定义一个新的数组:\(Height\)数组。

\(Height_i\)表示的是\(LCP(i,i+1)\)。

因此\(LCP(i,j)\)的结果就是\(min_{k=i}^{j-1}Height_i\),这似乎可以在知道\(Height\)数组的情况下用\(RMQ\)实现\(O(1)\)求解。

于是关键来了:如何求出\(Height\)数组。


如何求\(Height\)数组

首先我们要知道一个性质\(Height_{SA_i}\ge Height_{SA_{i-1}}\)。

这个性质我也不会证,反正它还是挺简单的,背一下就好了

这样一来,我们每次可以把\(Height_{SA_i}\)初始化为\(Height_{SA_{i-1}}\),然后每次尽量向外延长即可,这一过程似乎与\(Manacher\)算法有点类似。


代码

放一份求\(Height\)数组及\(LCP\)的模板代码:

class Class_SuffixArray
{
    private:
        int n,SA[N+5],Height[N+5],rk[N+5],pos[N+5],tot[N+5];
        inline void RadixSort(int S)//基数排序
        {
            register int i;
            for(i=0;i<=S;++i) tot[i]=0;
            for(i=1;i<=n;++i) ++tot[rk[i]];
            for(i=1;i<=S;++i) tot[i]+=tot[i-1];
            for(i=n;i;--i) SA[tot[rk[pos[i]]]--]=pos[i];
        }
        inline void GetSA(char *s)//后缀排序,求SA数组
        {
            register int i,k,Size=122,cnt=0;
            for(i=1;i<=n;++i) rk[pos[i]=i]=s[i-1];
            for(RadixSort(Size),k=1;cnt<n;k<<=1)
            {
                for(Size=cnt,cnt=0,i=1;i<=k;++i) pos[++cnt]=n-k+i;
                for(i=1;i<=n;++i) SA[i]>k&&(pos[++cnt]=SA[i]-k);
                for(RadixSort(Size),i=1;i<=n;++i) pos[i]=rk[i];
                for(rk[SA[1]]=cnt=1,i=2;i<=n;++i) rk[SA[i]]=(pos[SA[i-1]]^pos[SA[i]]||pos[SA[i-1]+k]^pos[SA[i]+k])?++cnt:cnt;
            }
        }
        inline void GetHeight(char *s)//求Height数组
        {
            register int i,j,k=0;
            for(i=1;i<=n;++i) rk[SA[i]]=i;//更新rk数组
            for(i=1;i<=n;++i)
            {
                if(k&&--k,!(rk[i]^1)) continue;//对于rk[i]=1的情况直接跳过
                j=SA[rk[i]-1];//找到上一个后缀的坐标
                while(i+k<=n&&j+k<=n&&!(s[i+k-1]^s[j+k-1])) ++k;//尽量拓展
                Height[rk[i]]=k;//存值
            }
        }
        class Class_RMQ//RMQ求区间最值
        {
            private:
                #define LogN 15
                int Log2[N+5],Min[N+5][LogN+5];
            public:
                inline void Init(int len,int *data)
                {
                    register int i,j;
                    for(i=2;i<=len;++i) Log2[i]=Log2[i>>1]+1;
                    for(i=1;i<=len;++i) Min[i][0]=data[i];
                    for(j=1;(1<<j-1)<=len;++j) for(i=1;i+(1<<j-1)<=len;++i) Min[i][j]=min(Min[i][j-1],Min[i+(1<<j-1)][j-1]);
                }
                inline int GetMin(int l,int r) {register int k=Log2[r-l+1];return min(Min[l][k],Min[r-(1<<k)+1][k]);}
        }RMQ;
    public:
        inline void Init(int len,char *s) {n=len,GetSA(s),GetHeight(s),RMQ.Init(n,Height);}//初始化
        inline int LCP(int x,int y) {return x^y?(rk[x]>rk[y]&&swap(x,y),RMQ.GetMin(rk[x]+1,rk[y])):n-x+1;}//求LCP,注意特判x=y的情况
};

原文地址:https://www.cnblogs.com/chenxiaoran666/p/SuffixArray.html

时间: 2024-10-07 05:06:40

后缀数组入门(二)——Height数组与LCP的相关文章

JavaScript - 一维数组、二维数组基础

<html> <head> <head> <body> <script language="javascript"> // 数组 var arr = ["wjp", 520, 'wcc', 13.14] ; for(var i=0; i<arr.length; i++){ alert(arr[i]); } // 数组为空undefined var arr2 = ["wjp",,51

java数组(一维数组,二维数组)

数组初始化: 1,动态初始化:数组定义与为数组分配空间和赋值的操作分开进行 2,静态初始化:在定义数组的同时就为数组元素分配空间并赋值 3,默认初始化:数组是引用类型,它的元素相当于类的成员变量,因此,数组分配空间之后 每个元素也被按照成员的规则被隐士的初始化值 ------------------------------------------------. 一维数组: package day06; import java.util.Date; /*** * 数组演示 * 数组的声明: * t

C#一维数组,二维数组,多维数组

所谓的数组,就是相同数据类型的元素的元素按一定顺数排列的集合,就是把有限个类型相同的变量用一名字命名,然后用编号区分他们的变量的集合,这个名字成为数组名,编号称为下标.组成数组的各个变量成为数组的分量,也称为数组的元素,有时也称为下标变量,数组是在程序设计中,为了处理方便,把具有相同类型的若干变量按有序的形式组织起来的一种形式.这些按序排列的同类数据元素的集合成为数组. 数组又分为一维数组,二维数组,多维数组 一维数组用一个整数来索引,多维数组用两个或多个整数来索引. 一.数据类型[] 变量名

16.10.20 4th 1蛤蟆爬井 2一维数组 3二维数组

摘要 1蛤蟆爬井 2一维数组 3二维数组 例子 井深10米, 蛤蟆白天爬5m,晚上落4米,求几天爬出来. //思路,用循环语句来做,for因为是未知次数所以排除,while 先判断后运行排除, dowhile,先爬在判断所以可以 int gaodu = 0; int tianshu = 0; boolean tianse = true; do{ if(tainse){ //白天爬5米 gaodu+=5; //爬完后改黑天 tianse = false; //天数+1 tianshu +=1; }

数组、二维数组

简单的数组.二位数组 1 package com.lovo; 2 3 import java.util.Scanner; 4 5 /** 6 * 二维数组 7 * 8 * @author 王启文 9 * 10 */ 11 12 public class D2 { 13 public static void main(String[] args) { 14 Scanner sc = new Scanner(System.in); 15 16 String[] names = { "赵大"

一维数组,二维数组,三维数组,数组与指针,结构体数组,通过改变指针类型改变访问数组的方式

 打印数组中的每个元素,打印每个元素的地址: #include <stdio.h> #include <stdlib.h> void main(void) { int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; for (int *p = a; p < a + 10;p++)  //指针类型决定4个字节 { printf("\n%p,%d", p, *p); } getchar(); } 指针数组 #inclu

黑马程序员----java学习笔记之数组、二维数组,附相关面试题

------<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! ------- 一:数组(掌握) (1)数组:存储同一种数据类型的多个元素的容器. (2)特点:每一个元素都有编号,从0开始,最大编号是长度-1. 编号的专业叫法:索引 (3)定义格式 A:数据类型[] 数组名; B:数据类型 数组名[]; 推荐是用A方

C/C++ 指针数组、二维数组

一. 二维数组 1> 初始化 int a[3][3] = { {1,0,0}, {0,1,0}, {0,0,1} }; // 这里是 {} , 而不是() 2> 将二维数组当做一维数组处理 void print_array( int * p, int row, int line) { for(int i=0; i<row; ++i) { for(int j=0; j<line; ++j) { printf("%d \n", *( p + i * row + j

C# 数组、一维数组、二维数组、多维数组、锯齿数组

C#  数组.一维数组.二维数组.多维数组.锯齿数组 一.数组: 如果需要使用同一类型的对象,就可以使用数组,数组是一种数据结构,它可以包含同一类型的多个元素.它的长度是固定的,如长度未知的情况下,请使用集合. 二.一维数组: 声明及初始化: class Program { static void Main(string[] args) { //方法一 int[] num = new int[3];//声明一个长度为3的值类型的数组: num[0] = 3;//为数组赋值: num[1] = 5

java基础中的一维数组和二维数组

1.数组:具有相同数据类型的一组数据的集合. 一维数组 2.创建一维数组 a.数组作为对象允许使用new关键字进行内存分配,使用数组之前,必须首先定义数组变量所属类型,即声明数组.  声明数组: 语法:数组元素类型   数组名字[]; 数组元素类型[]  数组名字: 注释:数组元素类型:决定了数组的数据类型,包括基本数据类型和费基本数据类型. 数组名字:为一个合法的标识符,自己去定义 []:指明该变量是一个数组类型变量,单个"[]"表示要创建的是一维数组. 例如:int arr[];