后缀数组(SA)

后缀数组

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]}\)

posted @ 2023-05-02 11:52  H_W_Y  阅读(51)  评论(0编辑  收藏  举报