字符串系列——SA

7 篇文章 0 订阅
1 篇文章 0 订阅

高一还不会SA就退役吧

例题

UOJ#35. 后缀排序
这是一道模板题。

读入一个长度为 n n n 的由小写英文字母组成的字符串,请把这个字符串的所有非空后缀按字典序从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置。位置编号为 1 1 1 n n n

除此之外为了进一步证明你确实有给后缀排序的超能力,请另外输出 n − 1 n - 1 n1 个整数分别表示排序后相邻后缀的最长公共前缀的长度。

输入格式
一行一个长度为 n n n 的仅包含小写英文字母的字符串。

输出格式
第一行 n n n 个整数,第 i i i 个整数表示排名为 i i i 的后缀的第一个字符在原串中的位置。

第二行 n − 1 n - 1 n1 个整数,第 i i i 个整数表示排名为 i i i 和排名为 i + 1 i + 1 i+1 的后缀的最长公共前缀的长度。

样例一
input
ababa

output
5 3 1 4 2
1 3 0 2

explanation
排序后结果为:

a
aba
ababa
ba
baba
限制与约定
1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1n105
时间限制: 1 s 1\texttt{s} 1s
空间限制: 256 MB 256\texttt{MB} 256MB

SA

一个卵用并不大的东西
SA能做的SAM基本都能做,除了O(1)求后缀的LCP
但有些题就是要这么搞也没办法

一些定义:
st:字符串
rank[i]:st[i…n]的字典序排名(显然各不相同)
sa[i]:排名为i的首字母出现位置(rank的逆数组),即sa[rank[i]]=i
hi[i](即height):st[sa[i-1]…n]和st[sa[i]…n]的LCP长度,hi[i]=h[sa[i]]
h[i]:st[sa[rank[i]-1]…n]和st[i…n]的LCP长度,h[i]=hi[rank[i]]
h[i]即表示以i为结尾的后缀和i排名的上一位的LCP长度

rank

首先是rank的求法
暴力肯定布星,考虑倍增求rank
在这里插入图片描述
每次把相邻的长度为2k段两段的rank,用二维桶排来求出新的rank(可重,但最终一定不重)
具体:先排个位,再排十位,因为邻接表的性质,所以要反着提

height

直接求height不方便,所以引入了h数组
h数组有一个非常显然且重要的性质:

h[i]≥h[i-1]-1

证明:
在这里插入图片描述
显然h[i]至少为h[i-1]删掉i-1,即至少为h[i-1]-1
简单又自然

有了这个性质后就可以线性求出h数组,然后可以求出height
(有可能sa[rank[i]-1]>i,所以两个都不能超出边界)

height的性质

可以利用height的性质来搞事
st[sa[i]…n]与st[sa[j]…n]的LCP=min(height[i+1…j])
证明:
设st[sa[i]…n]与st[sa[j]…n]的LCP长度为x,则x≥min(height[i+1…j])
若x>min(height[i+1…j]),则与字典序连续相违背,所以x≤min(height[i+1…j])
综上,x=min(height[i+1…j])


对应到原串中,st[i…n]和st[j…n]的LCP长度为min(height[rank[i]+1…rank[j]])(rank[i]<rank[j])
然而这题并没有用到这个性质
以后再填坑

code

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
using namespace std;

int n,i,j,k,l,len;
int A[100001];
int pre[100001];
int Ls[100001];
int st[100001];
int h[100001]; //h[i]=hi[rank[i]]
int hi[100001]; //the LCP of sa[i-1] and sa[i]   hi[i]=h[sa[i]]
int rank[200001];
int sa[200001];
int Rank[200001];
int Bz[26];
char ch;

int main()
{
//	freopen("a.in","r",stdin);
//	freopen("b.out","w",stdout);
//	freopen("UOJ35.in","r",stdin);
	
	ch=getchar();
	while (ch>='a' && ch<='z')
	{
		st[++n]=ch-'a';
		ch=getchar();
		
		Bz[st[n]]=1;
	}
	
	fo(i,0,25)
	Bz[i]+=Bz[i-1];
	
	fo(i,1,n)
	rank[i]=Bz[st[i]];
	
	k=1;
	while (k<=n)
	{
		fo(i,1,n)
		{
			pre[i]=Ls[rank[i+k]];
			Ls[rank[i+k]]=i;
		}
		l=n;
		fd(i,n,0)
		{
			while (Ls[i])
			{
				A[l--]=Ls[i];
				Ls[i]=pre[Ls[i]];
			}
		}
		
		fo(i,1,n)
		{
			pre[A[i]]=Ls[rank[A[i]]];
			Ls[rank[A[i]]]=A[i];
		}
		l=n;
		fd(i,n,0)
		{
			while (Ls[i])
			{
				A[l--]=Ls[i];
				Ls[i]=pre[Ls[i]];
			}
		}
		
		j=0;
		fo(i,1,n)
		Rank[i]=rank[i];
		fo(i,1,n)
		{
			if (i==1 || Rank[A[i]]!=Rank[A[i-1]] || Rank[A[i]+k]!=Rank[A[i-1]+k])
			++j;
			
			rank[A[i]]=j;
		}
		
		k+=k;
	}
	fo(i,1,n)
	sa[rank[i]]=i;
	
	fo(i,1,n)
	if (rank[i]>1)
	{
		h[i]=max(h[i-1]-1,0);
		
		while (i+h[i]-1<n && sa[rank[i]-1]+h[i]-1<n && st[i+h[i]]==st[sa[rank[i]-1]+h[i]])
		++h[i];
		
		if (h[i] && st[i+h[i]-1]!=st[sa[rank[i]-1]+h[i]-1])
		--h[i];
	}
	
	fo(i,2,n)
	hi[i]=h[sa[i]];
	
	fo(i,1,n)
	printf("%d ",sa[i]);
	printf("\n");
	fo(i,2,n)
	printf("%d ",hi[i]);
	printf("\n");
}

参考资料

https://www.cnblogs.com/heyujun/p/10300582.html
https://www.cnblogs.com/cjyyb/p/8335194.html
https://blog.csdn.net/cold_chair/article/details/62909232

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值