SA学习笔记

关于后缀排序这玩意应该都知道了.........

看这篇博客你可能需要一点前置知识.......建议先自行了解SA的一些定义.

梳理一下\(height\)数组的性质、求法以及\(LCP(i,j)\)的求法

约定几个前置定义:

\(Suffix(i)\) :表示后缀\(i\)

\(SA[i]\) : 表示排名第 \(i\) 的后缀

\(rk[i]\) :表示后缀\(i\)的排名

\(LCP(i,j)\) :表示后缀\(i\)和后缀\(j\)的最长公共前缀

\(str\):表示的就是给出的字符串,\(str[i]就表示第i个字符\)

\(height\)数组的介绍

\(height[i]表示的是排名为i的后缀与排名为i-1的后缀的最长公共前缀\)

表示出来即为 : \(height[i] = LCP(SA[i],SA[i - 1]);\)

\(height\)数组的性质:

  • \(height[i] >= height[i - 1] - 1\)(可以用来求\(height数组\))
  • 对于\(LCP(i,j)\),不妨令\(rk[i]\) < \(rk[j]\),那么\(LCP(i,j) = min(height[rk[i]+1],height[rk[i]+2],height[rk[i]+3]......height[rk[j]])\)

关于上面性质的证明:后缀数组 罗穗骞

这里就不再赘述了。

\(height\)数组的方法:

定义h数组:\(h[i] = height[ rk[i] ]\)。按照\(h[1] , h[2] , h[3]....h[n]的顺序求解\)

有一点点绕,\(rk[i]表示的是后缀i排序后的排名\)

\(height[rk[i]]\)就是当前\(后缀i排序后的排名 与 后缀i排序后的排名 - 1的最长公共前缀\)

我们依次遍历以\(str[1]\)为开头的后缀,以\(str[2]\)为开头的后缀.....以\(str[3]\)为开头的后缀,同时求解\(h[i]\)

放上代码来讲吧:

void GetHeight()
{
    int k=0;
    for(int i = 1 ; i <= n ; i ++)
    {
        if(k)k--;
        int j = SA[rk[i]-1];//获得排名 rk[i] - 1 的后缀的位置,i则是排名rk[i]的后缀的位置
        while(str[i + k] == str[j + k])k ++;//暴力匹配
        height[rk[i]] = k;//rk[i]表示的是后缀i的排名
    }
    return ;
}

这样子就可以求得\(height\)数组了

对于\(LCP(i,j)\)的求法

我们上面写了"对于\(LCP(i,j)\),不妨令\(rk[i]\) < \(rk[j]\),那么\(LCP(i,j) = min(height[rk[i]+1],height[rk[i]+2],height[rk[i]+3]....height[rk[j]])\)"
于是这就是一个经典的,RMQ可以维护的区间最值问题了。
RMQ O(\(nlog(n)\))预处理,O(1)查询即可 

ps.我写的这个后缀数组基本上都是写成函数的形式,方便您观看,也便于作为模板

Code

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

struct Pre{
	int data,id;//预处理要用的 
} P[500005];

struct Node {
	int K1,K2,id;
} S[500005];

char a[500005];
int SA[500005],rk[500005],height[500005];
int Min[500005][25];//RMQ要用
int n ;

int cmpP(Pre A,Pre B);
int cmpN(Node A, Node B);
void Sort();//排序
bool GetRank(int x);//排序
void GetHeight();
void DealRMQ();
void prepare();
int LCP(int l,int r);//给出的函数对应下面的顺序,方便查找 

int main()
{
	prepare();//预处理以及读入
	Sort();//进行后缀排序 
	for(int i = 1 ; i <= n ; i ++)SA[rk[i]] = i;//给SA数组赋值
	GetHeight();//获得height数组
	DealRMQ();//处理RMQ
	int ans = 0;
	return 0;
}

int cmpP(Pre A,Pre B)
{
	return A.data < B.data;
}

int cmpN(Node A, Node B)
{
	if(A.K1 != B.K1)return A.K1 < B.K1;
	else return A.K2 < B.K2;
}

void Sort()
{
	int x = 1;
	while( ! GetRank(x) )x << 1;//倍增
	return ;
}

bool GetRank(int x)
{
	for(int i = 1 ; i <= n ; i ++)
	{
		S[i].K1 = rk[i];
		if(i + x > n)S[i].K2 = 0 ;
		else S[i].K2 = rk[i + x];
		S[i].id = i;
	}
	sort(S + 1 , S + 1 + n , cmpN);//没有写基排
	S[0].K1 = -1 , S[0].K2 = -1;
	int now = 0;
	for(int i = 1 ; i <= n ; i ++)
	{
		if(S[i].K1 != S[i-1].K1 || S[i].K2 != S[i-1].K2)now ++;
		rk[S[i].id] = now;
	}
	if(now == n)return 1;//如果排名都不相同,排名完毕,返回1
	else return 0;//否则返回0
}

void GetHeight()
{
	int k = 0;
	for(int i = 1 ; i <= n ; i ++)
	{
		if(k) k --;
		int j = SA[rk[i] - 1];
		while(a[j + k] == a[i + k]) k ++;
		height[rk[i]] = k;
	}
	return ;
}

void DealRMQ()
{
	for(int i = 1 ; i <= n ; i ++)Min[i][0] = height[i];
	for(int j = 1 ; j <= log2(n); j ++)
		for(int i = 1 ; i + (1 << j) - 1 <= n ; i ++)
	Min[i][j] =	min(Min[i][j-1],Min[i + (1 << (j-1))][j - 1]);
}

void prepare()
{	
	cin >> a + 1;
	n = strlen(a + 1);
	for(int i = 1 ; i <= n ; i ++)
	{
		P[i].id = i ;
		P[i].data = a[i] - 'a' + 1;
	}
	sort(P + 1 , P + 1 + n , cmpP);
	for(int i = 1 , now = 0 ; i <= n ; i ++)
	{
		if(P[i].data != P[i-1].data)now ++;
		rk[P[i].id] = now;//处理一开始的排名,给rk数组赋初值
	}
	return ;
}

int LCP(int l,int r)
{
	int Les = min(rk[l],rk[r]) + 1;
	int Gre = max(rk[l],rk[r]);
	int k = log2(Gre - Les + 1);
	return min(Min[Les][k],Min[Gre - (1 << k) + 1][k]);
}
posted @ 2020-10-29 09:31  MYCui  阅读(166)  评论(0编辑  收藏  举报