前言

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

这篇博客的内容,主要建立于后缀排序的基础之上,进一步研究一个\(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+1}^jLCP(k,k-1)\)

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

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

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

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

\(Height\)数组

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

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

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

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

如何求\(Height\)数组

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

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

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

代码

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

  1. class Class_SuffixArray
  2. {
  3. private:
  4. int n,SA[N+5],Height[N+5],rk[N+5],pos[N+5],tot[N+5];
  5. inline void RadixSort(int S)//基数排序
  6. {
  7. register int i;
  8. for(i=0;i<=S;++i) tot[i]=0;
  9. for(i=1;i<=n;++i) ++tot[rk[i]];
  10. for(i=1;i<=S;++i) tot[i]+=tot[i-1];
  11. for(i=n;i;--i) SA[tot[rk[pos[i]]]--]=pos[i];
  12. }
  13. inline void GetSA(char *s)//后缀排序,求SA数组
  14. {
  15. register int i,k,Size=122,cnt=0;
  16. for(i=1;i<=n;++i) rk[pos[i]=i]=s[i-1];
  17. for(RadixSort(Size),k=1;cnt<n;k<<=1)
  18. {
  19. for(Size=cnt,cnt=0,i=1;i<=k;++i) pos[++cnt]=n-k+i;
  20. for(i=1;i<=n;++i) SA[i]>k&&(pos[++cnt]=SA[i]-k);
  21. for(RadixSort(Size),i=1;i<=n;++i) pos[i]=rk[i];
  22. 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;
  23. }
  24. }
  25. inline void GetHeight(char *s)//求Height数组
  26. {
  27. register int i,j,k=0;
  28. for(i=1;i<=n;++i) rk[SA[i]]=i;//更新rk数组
  29. for(i=1;i<=n;++i)
  30. {
  31. if(k&&--k,!(rk[i]^1)) continue;//对于rk[i]=1的情况直接跳过
  32. j=SA[rk[i]-1];//找到上一个后缀的坐标
  33. while(i+k<=n&&j+k<=n&&!(s[i+k-1]^s[j+k-1])) ++k;//尽量拓展
  34. Height[rk[i]]=k;//存值
  35. }
  36. }
  37. class Class_RMQ//RMQ求区间最值
  38. {
  39. private:
  40. #define LogN 15
  41. int Log2[N+5],Min[N+5][LogN+5];
  42. public:
  43. inline void Init(int len,int *data)
  44. {
  45. register int i,j;
  46. for(i=2;i<=len;++i) Log2[i]=Log2[i>>1]+1;
  47. for(i=1;i<=len;++i) Min[i][0]=data[i];
  48. 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]);
  49. }
  50. 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]);}
  51. }RMQ;
  52. public:
  53. inline void Init(int len,char *s) {n=len,GetSA(s),GetHeight(s),RMQ.Init(n,Height);}//初始化
  54. 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的情况
  55. };

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

  1. 062 01 Android 零基础入门 01 Java基础语法 07 Java二维数组 01 二维数组应用

    062 01 Android 零基础入门 01 Java基础语法 07 Java二维数组 01 二维数组应用 本文知识点:二维数组应用 二维数组的声明和创建 ? 出现空指针异常 数组的名字指向数组的第 ...

  2. 一维数组、二维数组——Java

    一. 一维数组 1.  数组是相同类型数据的有序集合 相同类型的若干个数据,按照一定先后次序排列组合而成 每个数组元素可以通过一个下标来访问它们 其中,每一个数据称作一个数组元素 2. 数组特点: 其 ...

  3. C#的一维数组和二维数组定义方式:

    一维数组: //一维数组定义与初始化 ,, };//第一种方式 , , }; //第二种方式 int[] one3; //第三种方式 one3=,,}; 二维数组: //二维数组定义与初始化 //不规 ...

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

    C#  数组.一维数组.二维数组.多维数组.锯齿数组 一.数组: 如果需要使用同一类型的对象,就可以使用数组,数组是一种数据结构,它可以包含同一类型的多个元素.它的长度是固定的,如长度未知的情况下,请 ...

  5. c#简单实现二维数组和二维数组列表List&lt;&gt;的转置

    刚看到网上一篇文章里用sql实现了行列转置.sql server 2005/2008只用一个pivot函数就可以实现sql server 2000很多行的复杂实现.提到转置,立刻想起还在求学阶段曾经做 ...

  6. java - day005 - 数组工具类, 数组复制,二维数组,变量,方法, 面向对象

    1. java.util.Arrays  数组工具类    Arrays.toString (数组) 数组值链接字符串 Arrays.sort(数组) 基本类型: 优化的快速排序 引用类型: 优化的合 ...

  7. Java数组之二维数组

    Java中除了一维数组外,还有二维数组,三维数组等多维数组.本文以介绍二维数组来了解多维数组. 1.二维数组的基础 二维数组的定义:二维数组就是数组的数组,数组里的元素也是数组. 二维数组表示行列二维 ...

  8. java基础5 (一维)数组和二维数组

    本文知识点(目录): 一维数组(一维数组的概念.优点.格式.定义.初始化.遍历.常见异常.内存分析以及常见操作(找最大值.选择排序.冒泡排序等等))    二维数组(二维数组的遍历.排序.查找.定义. ...

  9. JS中:数组和二维数组、MAP、Set和枚举的使用

    1.数组和二维数组:   方法一: var names = ['Michael', 'Bob', 'Tracy']; names[0];// 'Michael' 方法二: var mycars=new ...

  10. 二维数组,锯齿数组和集合 C# 一维数组、二维数组(矩形数组)、交错数组(锯齿数组)的使用 C# 数组、多维数组(矩形数组)、锯齿数组(交叉数组)

    二维数组,锯齿数组和集合 一.二维数组 二维数组:一维数组----豆角二维数组----表格 定义:1.一维数组:数据类型[] 数组变量名 = new 数据类型[数组长度];数据类型[] 数组变量名 = ...

随机推荐

  1. 一个简单的基于MINI2440开发板的启动代码

    1. S3C2440大概的启动流程(NAND启动): ①设置CPU为SVC模式 ②关闭看门狗 ③屏蔽中断 ④关闭MMU ⑤初始化时钟 ⑥初始化内存(SDRAM) ⑦初始化栈指针(SP, R13) ⑧初 ...

  2. vue-踩过的坑

    1)引入组件时路径一定要是./ or ../开头 import Goback from './public/goback.vue' 2)这类输入框绑定的值不是:value 不是 :value 不然数据 ...

  3. ORACLE 12.2 RAC TNS-12520 遭遇连接风爆 (connection storm)

    故障现象:数据库迁移到新环境刚过两天.今天生产核心数,断断继续的告警连接不上.这是问题! 如理思路:1.查看alert日志,日志无报错                  2.查看连接数,参数设置的20 ...

  4. vue vue-resource网络请求

    在使用get/post 网络请求,需要下载插件 "vue-resource" npm install vue-resource -s 在路由要导入及注册 import Vue fr ...

  5. SQL 还原或备份失败数据库变成单个用户模式无法访问

    还原数据失败了,数据库变成单个用户模式,无法操作了,执行下面的语句就可以了 USE master GO DECLARE @SQL VARCHAR(MAX); SET @SQL='' SELECT @S ...

  6. python_元组 学习

    一.创建元组 代码: name=(‘chinese’,’gansu’,’beijing’) 创建空元组 name=() 元组中只包含一个元素时,需要在玄素后面加逗号(,)消除歧义: name=(‘ch ...

  7. 所有节点配置NTP服务

    主节点: 打开vim /etc/ntp.conf文件 For more information about this file, see the man pages # ntp.conf(), ntp ...

  8. 统计学howto

    统计之都 http://cos.name/ 让你拥有超能力:程序员应该掌握的统计学公式 Statistical Formulas For Programmers http://www.evanmill ...

  9. 快速创建jquery插件

    介绍 什么是插件? 插件我们见得很多了,比如浏览器上我们会安装一些去除广告的插件.美化页面的插件等等. 在前端,我们也常常写一些jquery插件来使用.来添加我们常常写的一些功能,方便使用. 为什么要 ...

  10. git如何使用

    Git是分布式的,但多数时候仍然要使用中央仓库作为所有开发者的交互中心,和svn一样,开发人员仍要在本地写代码并提交到中央服务器.Git相较于svn最大的优势就在于其强大的分支系统,而git的工作流程 ...