首页 > 其他分享 >后缀数组(SA)

后缀数组(SA)

时间:2023-05-02 12:00:42浏览次数:40  
标签:cnt 后缀 height 数组 SA sa rk

后缀数组

Suffix Array
时间复杂度\(O(n \log n)\)
主要涉及到三个数组:
\(sa[i]\)表示后缀数组,排名第\(i\)个的后缀编号
\(rk[i]\)表示第\(i\)个后缀的排名
\(height[i]\)表示排名为\(i\)和\(i-1\)的后缀的lcp(最长公共前缀)

易得:\(sa[rk[i]]=rk[sa[i]]=i\)

求后缀数组get_sa

倍增实现,时间复杂度\(O(n \log_2n)\)

定义:

  1. \(x[i]\),桶数组,里面存的是编号
  2. \(fu[i]\),辅助数组,里面记录上一次的sa与tong
  3. \(cnt[i]\),记录每一个桶中的数量

伪代码

void get_sa(){
  按第一个字母排序:
  1~n 编桶号,记录cnt
  1~m 求cnt的前缀和
  n~1 倒序枚举,保序,按第一个字母排序,sa[cnt[x[i]]--]=i;
  
  1~n,k<<=1,倍增{
    1. 按下一个关键字排序
	memset,cnt
	1~n fu->sa,把sa复制一遍
	1~n 向右移动k位,cnt[x[fu[i]+k]]++;
	1~m 求cnt前缀和
	n~1 倒序枚举更新sa
	
    2. 在按照之前排好的顺序排一次
	
    3. 把后缀数组放入桶数组中,这样桶中就存的是前面已排序的部分的编号了
	1~n fu->x
	m=0;
	1~n if前面的相同且这k位也相同,存入已有的桶中
	    else新建一个桶存入
		
    m==n? 已经排好了
  }
}

求height数组

时间复杂度\(O(n)\)

定义

\(height[i]=lcp(sa[i],sa[i-1])\)

定理

\(height[rk[i]] \ge height[rk[i-1]]-1\)
后缀1为全数组

证明:

  1. 当\(height[i] \le 1\)时,定理一定成立
  2. 当\(height[i] \gt 1\)时
    \(lcp(sa[rk[i-1]],sa[rk[i-1]-1])=height[rk[i-1]] \gt 1\)
    所以说后缀\(i-1\)和后缀\(sa[rk[i-1]-1]\)有着长度为\(height[rk[i-1]]\)的公共前缀
    我们不妨设这个公共前缀为\(aA\),其中\(a\)为一个字符,\(A\)为一个字符串
    那么后缀\(i-1\)为\(aAD\),后缀\(sa[rk[i-1]-1]\)为\(aAB(B,D都为字符串,B可能为空串)\)
    所以后缀\(i\)为\(AD\),且存在后缀\((sa[rk[i-1]-1]+1)\)为\(AB\),则\(AB \lt AD\)
    由于\(sa[rk[i]-1] \lt AD\)
    所以\(AB \le sa[rk[i]-1] \lt AD\)
    而\(AB\)和\(AD\)有公共前缀\(A\)
    所以\(sa[rt[i]-1]\)和\(i\)一定有公共前缀\(A\)
    所以\(height[rk[i]] \ge height[rk[i-1]]-1\)

得证

伪代码

void get_height(){
  1~n 找rk
  1~n 枚举后缀{
    如果是第一个 continue;
	k--;
	找到上一个
	while开始匹配
	更新height
  }
}

例题:P3809 【模板】后缀排序

题目描述

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn=2e6+10;
char s[maxn];
int n,m,x[maxn],fu[maxn],sa[maxn],cnt[maxn];

void get_sa(){
  for(int i=1;i<=n;i++) cnt[x[i]=s[i]]++;
  for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
  for(int i=n;i>=1;i--) sa[cnt[x[i]]--]=i;
  for(int k=1;k<=n;k<<=1){
  	memset(cnt,0,sizeof(cnt));
  	for(int i=1;i<=n;i++) fu[i]=sa[i];
  	for(int i=1;i<=n;i++) cnt[x[fu[i]+k]]++;
  	for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
  	for(int i=n;i>=1;i--) sa[cnt[x[fu[i]+k]]--]=fu[i];
  	memset(cnt,0,sizeof(cnt));
  	for(int i=1;i<=n;i++) fu[i]=sa[i];
  	for(int i=1;i<=n;i++) cnt[x[fu[i]]]++;
  	for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
  	for(int i=n;i>=1;i--) sa[cnt[x[fu[i]]]--]=fu[i];
  	for(int i=1;i<=n;i++) fu[i]=x[i];
	m=0;
	for(int i=1;i<=n;i++)
	  if(fu[sa[i]]==fu[sa[i-1]]&&fu[sa[i]+k]==fu[sa[i-1]+k]) x[sa[i]]=m;
	  else x[sa[i]]=++m; 
	if(m==n) break;
  }
}
/*
int height[maxn],rk[maxn];
void get_height(){
  for(int i=1;i<=n;i++) rk[sa[i]]=i;
  for(int i=1,k=0;i<=n;i++){
  	if(rk[i]==1) continue;
  	if(k) k--;
  	int j=sa[rk[i]-1];
  	while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
  	height[rk[i]]=k;
  }
}*/
int main(){
  /*2023.5.2 hewanying P3809 【模板】后缀排序 后缀数组SA*/ 
  scanf("%s",s+1);
  n=strlen(s+1);m=122;
  get_sa();
  for(int i=1;i<=n;i++)
    printf("%d ",sa[i]);
  printf("\n");
  return 0;
}

关于后缀树组的其他定理

\(lcp(sa[i],sa[j])= \min{height[i+1 \to j]}\)
不同子串的个数:\(\frac{n(n+1)}{2}-\sum_{i=2}^{n}{height[i]}\)

标签:cnt,后缀,height,数组,SA,sa,rk
From: https://www.cnblogs.com/hewanying0622/p/17367416.html

相关文章

  • 分别使用SAD匹配,NCC匹配,SSD匹配三种算法提取双目图像的深度信息
    1.算法仿真效果matlab2022a仿真结果如下:     2.算法涉及理论知识概要       深度学习的蓬勃发展得益于大规模有标注的数据驱动,有监督学习(supervisedlearning)推动深度模型向着性能越来越高的方向发展。但是,大量的标注数据往往需要付出巨大的人力成本,越来......
  • Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple ERROR: Could not fi
    命令行输入:pipinstallmediapipe报错:Lookinginindexes:https://pypi.tuna.tsinghua.edu.cn/simpleERROR:Couldnotfindaversionthatsatisfiestherequirementmediapipe(fromversions:none)ERROR:Nomatchingdistributionfoundformediapipe查看了网......
  • RabbitMQ安装Delayed Message 插件
    在官网:https://www.rabbitmq.com/community-plugins.html点击:下载好之后就是一个解压好的文件:然后在将这个文件复制到rabiitmq/plugins里面:cp/Users/sixcandy/Downloads/rabbitmq_delayed_message_exchange-3.10.2.ez/opt/homebrew/Cellar/rabbitmq/3.10.2/plugins1进入rabi......
  • 代码笔记27 numpy和pytorch中的多维数组切片
    原来还可以用数组切数组,我算是长见识了。不多说了,直接上代码应该可以明白importnumpyasnpxyz=np.arange(36).reshape(3,4,3)B,N,C=xyz.shapefarthest=np.random.randint(0,N,size=B)#torch.randint(0,N,(B,),dtype=torch.long)#初始时随机选择一点(B......
  • 更改歌曲后缀-代码备忘录
    packagecom.java1234.todo;importjava.io.*;importjava.util.HashMap;importjava.util.Objects;importjava.util.Properties;importjava.util.regex.Matcher;importjava.util.regex.Pattern;/***//沈亦晨-爱人错过(小提琴版).flac//留下**//......
  • springSecurity过滤器之AnonymousAuthenticationFilter
    SpringSecurity提供了匿名登录功能,让我们不登录也能访问。比如/anoy路径及子路径都能匿名访问,配置如下:@ConfigurationpublicclassMySecurityConfigextendsWebSecurityConfigurerAdapter{@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException......
  • c99之 柔性数组成员
    在讲述柔性数组成员之前,首先要介绍一下不完整类型(incompletetype)。不完整类型是这样一种类型,它缺乏足够的信息例如长度去描述一个完整的对象。6.2.5 Typesincompletetypes(typesthatdescribeobjectsbutlackinformationneededtodeterminetheirsizes).C与C++关于不完......
  • rust中动态数组的引用和切片
    真逆天这个b语法1切片与String切片类似,动态数组Vec也能切片,通过&取切片般如果Vec是可变的话,那么他的切片就是不可变的/只读的注意:切片和&Vec是不同的类型,后者仅仅是Vec的引用,并可以通过解引用直接获取Vecfnmain(){letmutv=vec![1,2,3];letslice=&......
  • 7-014-(LeetCode- 718) 最长重复子数组
    1.题目读题 考查点 2.解法思路 代码逻辑 具体实现113.总结......
  • 7-006-(LeetCode- 152) 乘积最大子数组
    1.题目读题 考查点 2.解法思路 代码逻辑 具体实现113.总结......