五分钟搞懂后缀数组!

为什么学后缀数组

后缀数组是一个比较强大的处理字符串的算法,是有关字符串的基础算法,所以必须掌握。 
学会后缀自动机(SAM)就不用学后缀数组(SA)了?不,虽然SAM看起来更为强大和全面,但是有些SAM解决不了的问题能被SA解决,只掌握SAM是远远不够的。 
……

有什么SAM做不了的例子? 
比如果求一个串后缀的lcp方面的应用,这是SA可以很方便的用rmq来维护,但是SAM还要求lca,比较麻烦,还有就是字符集比较大的时候SA也有优势。

现在这里放道题,看完这个blog可能就会做了!: 
你可想想这道题:你有一个01串S,然后定义一个前缀最右边的位置就是这个前缀的结束位置。现在有q多个询问,每个询问结束位置在l~r中不同前缀的最长公共后缀是多长? 
|S|,q100000|S|,q≤100000 
时限4s

而下面是我对后缀数组的一些理解

构造后缀数组——SA

先定义一些变量的含义

Str :需要处理的字符串(长度为Len) 
Suffix[i] :Str下标为i ~ Len的连续子串(即后缀) 
Rank[i] : Suffix[i]在所有后缀中的排名 
SA[i] : 满足Suffix[SA[1]] < Suffix[SA[2]] …… < Suffix[SA[Len]],即排名为i的后缀为Suffix[SA[i]] (与Rank是互逆运算) 
好,来形象的理解一下 
这就是Rank和SA
后缀数组指的就是这个SA[i],有了它,我们就可以实现一些很强大的功能(如不相同子串个数、连续重复子串等)。如何快速的到它,便成为了这个算法的关键。而SA和Rank是互逆的,只要求出任意一个,另一个就可以O(Len)得到。 

现在比较主流的算法有两种,倍增和DC3,在这里,就主要讲一下稍微慢一些,但比较好实现以及理解的倍增算法(虽说慢,但也是O(Len logLen))的。

进入正题——倍增算法

倍增算法的主要思想 :对于一个后缀Suffix[i],如果想直接得到Rank比较困难,但是我们可以对每个字符开始的长度为2k2k的字符串求出排名,k从0开始每次递增1(每递增1就成为一轮),当2k2k大于Len时,所得到的序列就是Rank,而SA也就知道了。O(logLen)枚举k 
这样做有什么好处呢? 
设每一轮得到的序列为rank(注意r是小写,最终后缀排名Rank是大写)。有一个很美妙的性质就出现了!第k轮的rank可由第k - 1轮的rank快速得来! 
为什么呢?为了方便描述,设SubStr(i, len)为从第i个字符开始,长度为len的字符串我们可以把第k轮SubStr(i, 2k2k)看成是一个由SubStr(i, 2k12k−1)和SubStr(i + 2k12k−1, 2k12k−1)拼起来的东西。类似rmq算法,这两个长度而2k12k−1的字符串是上一轮遇到过的!当然上一轮的rank也知道!那么吧每个这一轮的字符串都转化为这种形式,并且大家都知道字符串的比较是从左往右,左边和右边的大小我们可以用上一轮的rank表示,那么……这不就是一些两位数(也可以视为第一关键字和第二关键字)比较大小吗!再把这些两位数重新排名就是这一轮的rank。 
我们用下面这张经典的图理解一下: 
就像一个两位数的比较
相信只要理解字符串的比较法则(跟实数差不多),理解起来并不难。#还有一个细节就是怎么把这些两位数排序?这种位数少的数进行排序毫无疑问的要用一个复杂度为长度*排序数的个数的优美算法——基数排序(对于两位数的数复杂度就是O(Len)的)。 
基数排序原理 : 把数字依次按照由低位到高位依次排序,排序时只看当前位。对于每一位排序时,因为上一位已经是有序的,所以这一位相等或符合大小条件时就不用交换位置,如果不符合大小条件就交换,实现可以用”桶”来做。(叙说起来比较奇怪,看完下面的代码应该更好理解,也可以上网查有关资料) 
好了SA和Rank(大写R)到此为止就处理好了。(下面有详解代码!)。但我们发现,只有这两样东西好像没什么用,为了处理重复子串之类的问题,我们就要引入一个表示最长公共前缀的新助手Height数组!

构造最长公共前缀——Height

同样先是定义一些变量

Heigth[i] : 表示Suffix[SA[i]]和Suffix[SA[i - 1]]的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀 
H[i] : 等于Height[Rank[i]],也就是后缀Suffix[i]和它前一名的后缀的最长公共前缀 
而两个排名不相邻的最长公共前缀定义为排名在它们之间的Height的最小值。 
跟上面一样,先形像的理解一下: 
这就是Height

高效地得到Height数组

如果一个一个数按SA中的顺序比较的话复杂度是O(N2N2)级别的,想要快速的得到Height就需要用到一个关于H数组的性质。 
H[i] ≥ H[i - 1] - 1! 
如果上面这个性质是对的,那我们可以按照H[1]、H[2]……H[Len]的顺序进行计算,那么复杂度就降为O(N)了! 
让我们尝试一下证明这个性质 : 设Suffix[k]是排在Suffix[i - 1]前一名的后缀,则它们的最长公共前缀是H[i - 1]。都去掉第一个字符,就变成Suffix[k + 1]和Suffix[i]。如果H[i - 1] = 0或1,那么H[i] ≥ 0显然成立。否则,H[i] ≥ H[i - 1] - 1(去掉了原来的第一个,其他前缀一样相等),所以Suffix[i]和在它前一名的后缀的最长公共前缀至少是H[i - 1] - 1。 
仔细想想还是比较好理解的。H求出来,那Height就相应的求出来了,这样结合SA,Rank和Height我们就可以做很多关于字符串的题了!

代码——Code

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 const int N=100010;
 6 int n,m,rank[N],sa[N],buc[N],id[N],height[N]; 
 7 char s[N];
 8 /*------------------------------------------------------------
 9 rank[i] 第i个后缀的排名; 
10 sa[i] 排名为i的后缀位置; 
11 buc[i] 计数排序辅助数组;
12 height[i] 排名为i的后缀与排名为(i-1)的后缀的LCP;
13 id[i] 倍增中后半段字符串位置(计数排序中的第二关键字);
14 s为原串
15 ------------------------------------------------------------*/
16 inline void qsort(){
17     //rank第一关键字,id第二关键字。
18     for(int i=0;i<=m;++i) buc[i]=0;
19     for(int i=1;i<=n;++i) ++buc[rank[id[i]]];
20     for(int i=1;i<=m;++i) buc[i]+=buc[i-1];
21     for(int i=n;i>=1;--i) sa[buc[rank[id[i]]]--]=id[i]; 
22     //计数排序,把新的二元组排序
23 }
24 //通过二元组两个下标的比较,确定两个子串是否相同
25 inline int cmp(int x,int y,int l){return id[x]==id[y]&&id[x+l]==id[y+l];}
26 int main() {
27     scanf("%s",s+1);
28     n=strlen(s+1);
29     for(int i=1;i<=n;++i) rank[i]=s[i],id[i]=i;
30     m=127,qsort();//一开始是以单个字符为单位,所以(m = 127)
31     for(int l=1,p=1,i;p<n;l<<=1,m=p){
32         //l 当前一个子串的长度; m 当前离散后的排名种类数
33         //当前的id(第二关键字)可直接由上一次的sa的得到
34         //更新sa值,并用id暂时存下上一轮的rank(用于cmp比较)
35         for(p=0,i=n-l+1;i<=n;++i) id[++p]=i;//长度越界,第二关键字为0
36         for(i=1;i<=n;++i) if (sa[i]>l) id[++p]=sa[i]-l;
37         qsort(),swap(rank,id),rank[sa[1]]=p=1;
38         //用已经完成的SA来更新与它互逆的rank,并离散rank
39         for(i=2;i<=n;++i) rank[sa[i]]=cmp(sa[i],sa[i-1],l)?p:++p;
40     }
41     //LCP  这个知道原理后就比较好理解程序
42     int j,k=0;
43     for(int i=1;i<=n;height[rank[i++]]=k)
44     for(k=k?k-1:k,j=sa[rank[i]-1];s[i+k]==s[j+k];++k);
45     return 0;
46 }
Code

4个比较基础的应用

Q1:一个串中两个串的最大公共前缀是多少? 
A1:这不就是Height吗?用rmq预处理,再O(1)查询。 
 
Q2:一个串中可重叠的重复最长子串是多长? 
A2:就是求任意两个后缀的最长公共前缀,而任意两个后缀的最长公共前缀都是Height 数组里某一段的最小值,那最长的就是Height中的最大值。 
 
Q3:一个串种不可重叠的重复最长子串是多长? 
A3:先二分答案,转化成判别式的问题比较好处理。假设当前需要判别长度为k是否符合要求,只需把排序后的后缀分成若干组,其中每组的后缀之间的Height 值都不小于k,再判断其中有没有不重复的后缀,具体就是看最大的SA值和最小的SA值相差超不超过k,有一组超过的话k就是合法答案。 
 
A4:一个字符串不相等的子串的个数是多少? 
Q4:每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同的前缀的个数。而且可以发现每一个后缀Suffix[SA[i]]的贡献是Len - SA[i] + 1,但是有子串算重复,重复的就是Heigh[i]个与前面相同的前缀,那么减去就可以了。最后,一个后缀Suffix[SA[i]]的贡献就是Len - SA[k] + 1 - Height[k]。 
对于后缀数组更多的应用这里就不详细阐述,经过思考后每个人都会发现它的一些不同的用途,它的功能也许比你想象中的更强大!

最开始的那道题

先搬下来。。。

你可想想这道题:你有一个01串S,然后定义一个前缀最右边的位置就是这个前缀的结束位置。现在有很多个询问,每q个询问结束位置在l~r中不同前缀的最长公共后缀是多长? 
|S|,q100000|S|,q≤100000 
时限4s

简单思路:首先可以把字符串反过来就是求后缀的最长公共前缀了,可以用SA求出height数组,然后用rmq预处理之后就是求两个位置间的最小值。然后对于一个区间,显然只有在SA数组中相邻的两个串可以贡献答案。 
对于区间询问的问题可以用莫队处理,然后考虑加入一个后缀应该怎么处理,我们可以维护一个按SA数组排序的链表。假设我们先把所有位置的SA全部加入,然后按顺序删除,重新按顺序加入时就可以O(1)完成修改。那么按照这个思路我们可以用固定左端点的并查集,做到只加入,不删除,然后用O(nn−−√+nlogn)O(nn+nlogn)的复杂度完成这道题。

*可能后面的处理方式比较麻烦,如果直接用splay维护区间中的后缀的话可以做到O(nn−−√logn)O(nnlogn),这个方法就比较直观,而SAM在个问题上还是有点无力的。这题只是为了说明SA相比于SAM还是有他的独到之处,特别是在处理后缀的lcp之类的问题上。

结束

 

转载于:https://www.cnblogs.com/Sparks-Pion/p/9558888.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/249612.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

spring-core

spring最核心的组件是BeanFactory&#xff0c;看了源码才发现&#xff0c;BeanFactory并非定义在spring-core中&#xff0c;那spring-core都有啥东东&#xff1f; spring-core主要提供以下服务&#xff0c;为BeanFactory的定义提供基础服务。 1, ConversionService Conversi…

nginx配置静态文件过期时间

1. 编辑虚拟主机配置文件/usr/local/nginx/conf/vhosts/huangzhenping.conf 说明&#xff1a;采用location方式 12345678910location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)${access_log off;expires 1d;}location ~ \.(js|css){access_log off;expires 1d;}2. 检查配置文件&#x…

vue 移动端在div上绑定click事件 失效

在.vue的文件中使用了better-scroll&#xff0c;在div标签上绑定click事件后&#xff0c;无效。 原因&#xff1a;使用了better-scroll&#xff0c;默认它会阻止touch事件。所以在配置中需要加上click: true 即可解决 mounted(){this.$nextTick(() > {let bscrollDom this.…

Java中的钩子方法

钩子方法是啥 钩子顾名思义就是用来挂东西的。那么要挂东西必须有个被挂的东西&#xff0c;要不就是铁环、要不就是墙的边沿。所以要能挂住东西必须要有个被勾住的铁环&#xff0c;要一个钩子。那么在java中也是同样的原理&#xff0c;你首先需要一个被挂在的东西&#xff0c;一…

启动tomcat出现too many connections的原因及解决方法

感谢分享&#xff0c;原文地址&#xff1a;http://blog.sina.com.cn/s/blog_e7e07ec30102vsba.html一、原因 产生too many connections 的直接原因是因为数据库提供的连接被全部占满了。数据库可以提供多少连接&#xff0c;可以再my.cnf(linux)或者my.ini(windows)下设定。这个…

Spring Beans 初始化流程分析

测试用例 依然使用这个官网上的用例&#xff0c;来进行调试&#xff1b; Person.java package org.shangyang.spring.container;/**- - author shangyang**/public class Person {String name;Person spouse;public String getName() {return name;}public void setName(Stri…

剑指offer(65)矩阵中的路径

题目描述 请设计一个函数&#xff0c;用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始&#xff0c;每一步可以在矩阵中向左&#xff0c;向右&#xff0c;向上&#xff0c;向下移动一个格子。如果一条路径经过了矩阵中的某一个…

VSCode中怎么改变文件夹的图标

昨天更新了VSCode后我的文件夹图标莫名其妙的没有了&#xff0c;变成了下图这样 看着真的让我难受的头皮发麻&#xff0c;本来打代码就头发少&#xff0c;难道非要让我变成秃头&#xff0c;不可能不可能&#xff0c;所以我找了找怎么解决 来&#xff0c;各位看官上眼 如图所示 …

jdk1.8以前不建议使用其自带的Base64来加解密

JDK1.8之前的base64是内部测试使用的代码&#xff0c;不建议生产环境使用&#xff0c;而且未来可能会移除&#xff0c; JDK1.8提供最新可以正式使用的Base64类&#xff0c; 不要使用JDK中自带的sun.misc.BASE64Decoder这个类去BASE64&#xff0c; 这个会在后面多加换行。使用ap…

Redis的五大数据类型

1.String&#xff08;字符串&#xff09; String是Redis最基本的类型&#xff0c;一个Key对应一个Value。 String类型是二进制安全的&#xff0c;意思是Redis的String可以包含任何数据&#xff0c;比如jpg图片或者序列化的对象。 String类型是Redis最基本的数据类型&#xff0c…

springxml解析

1.XML验证模式的认识 首先XML的验证模式有两种&#xff1a;DTD和XSD。 DTD文档类型定义&#xff0c;是XML约束模式语言。它是为了保证XML文档格式正确有效的方法。通过XML文档和DTD文档的比较来判断XML是否符合规范。(现在我很少见&#xff0c;不知道是不是淘汰了) 举个例子&…

jq函数绑定与解绑

最近学到几个新的jq函数 1、bind&#xff08;&#xff09;绑定函数 2、unbind&#xff08;&#xff09;解绑函数 3、add() .给元素追加字符串 4、addClass() 给某元素增加class属性值转载于:https://www.cnblogs.com/bigwang1126/p/9566556.html

微信小程序时间标签与范围联动设计实现

微信小程序时间标签与范围联动设计实现&#xff1f;最近忙于一个有关数据管理的微信小程序开发&#xff0c;遇到了上图情况&#xff0c;虽然很简单&#xff0c;还是整理一下。若有错误&#xff0c;请广大朋友们指正。 使用微信小程序组件radio-group、picker&#xff0c;用wxss…

github中的watch、star、fork的作用

在每个 github 项目的右上角&#xff0c;都有三个按钮,分别是 watch、star、fork&#xff0c;但是有些刚开始使用 github 的同学&#xff0c;可能对这三个按钮的使用却不怎么了解&#xff0c;包括一开始使用 github 的我也是如此&#xff0c;这篇博客&#xff0c;结合自己的理解…

docker 操作 记录

docker ps #查看当前docker容器 docker exec -it 容器名称 sh 进入docker容器 docker stop 停止docker容器转载于:https://www.cnblogs.com/objects/p/9569299.html

关于群论证明费马小定理?

这篇博客就是讲证费马的&#xff0c;没什么意思。 既然是要用群论证明费马小定理&#xff0c;那么我们先用数论证明一下。 (以下的 p 为一个质数) 首先我们考虑 一个前置定理&#xff1a; 第一个证明 若 $(c,p) 1$ (即 c 与 p 的 gcd 为 1)&#xff0c;且 $ac ≡ bc (mod\ p)$ …

spring 源码-context

1 spring-context 模块概要 该模块主要实现在spring-beans 模块的扩展&#xff0c;主要对aop支持及el表达式的实现 分析示例 public static void main(String[] args){ClassPathXmlApplicationContext context new ClassPathXmlApplicationContext("spring-aop.xml"…

标示符和关键字的总结--希望别再犯错

&#xff08;一&#xff09;Java关键字的表 一共50个关键字&#xff0c;如下表 其中绝大部分关键词是Java语法发布之初就约定好的&#xff0c;少部分关键词是随Java语言发展后加入的。 strictfp JDK1.2 加入 assert JDK1.4 加入 enum JDK5.0 加入 还有少数单词&#xff0c;目前…

历届试题 打印十字图

问题描述 小明为某机构设计了一个十字型的徽标&#xff08;并非红十字会啊&#xff09;&#xff0c;如下所示&#xff1a; ..$$$$$$$$$$$$$....$...........$..$$$.$$$$$$$$$.$$$$...$.......$...$$.$$$.$$$$$.$$$.$$.$...$...$...$.$$.$.$$$.$.$$$.$.$$.$.$...$...$.$.$$.$.$.…

Spring BeanDefinition

BeanDefinition&#xff0c;顾名思义&#xff0c;是一个对象(Bean)在Spring中描述&#xff0c;其核心类图&#xff1a; 从类图我们详细了解BeanDefinition。 BeanDefinition接口继承自BeanMetadataElement和AttributeAccessor两个接口。 BeanMetadataElement&#xff1a;bean…