前言

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

这篇博客的内容,主要建立于后缀排序的基础之上,进一步研究一个\(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\)的模板代码:

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的情况
};

后缀数组入门(二)——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. STL-----c++标准模板

    一.排序和检索 1.sort(v.begin,v.end) 2.lower_bound(v.begin,v.end,x)

  2. vue 子组件调用父组件的函数

    子组件调用父组件的函数,使用$emit(eventName,[...args]),触发当前实例上的事件.附加参数都会传给监听器回调. 子组件 <template> <div> ...

  3. [转]分享20佳好玩的 jQuery 游戏

    本文转自:http://www.cnblogs.com/lhb25/archive/2011/04/17/2001089.html jQuery是时下最流行的 JavaScript 库.现在,除了HT ...

  4. POJ 3694——Network——————【连通图,LCA求桥】

    Network Time Limit:5000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u Submit Stat ...

  5. HDU 4357——String change——————【规律题】

    String change Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Tot ...

  6. 设置windows 10 wifi

    1.cmd 管理员 2.执行:netsh wlan set hostednetwork mode=allow ssid=test key=123456789 3.执行:netsh wlan start ...

  7. Wpf鼠标点击坐标转为屏幕坐标/后台重新设置在Canvas和Grid上的位置

    Point getP = PointToScreen(Mouse.GetPosition(this)); DockPanel.SetValue(Canvas.LeftProperty, 1.0); D ...

  8. hibernate课程 初探一对多映射2-6 测试-添加和查询学生信息

    package com.ddwei.entity; import java.util.Set; import org.hibernate.Session; import org.hibernate.T ...

  9. If you want the rainbow, you have to deal with the rain.

    If you want the rainbow, you have to deal with the rain.想要彩虹,就先忍受雨水.

  10. Android GreenDAO 3.0 不修改版本号的情况下增加、删除表、添加字段

    最近项目中使用了GreenDAO的3.0以上的版本,出现需要增加删除表的需求,刚开始用,发现官方对增加和删除的方法是每次去修改数据库版本号,版本一旦升级,那么原来数据库中的表会全部删除再重建.太麻烦, ...