字符串系列——SA

高一還不會SA就退役吧

例題

UOJ#35. 後綴排序
這是一道模板題。

讀入一個長度爲 nn 的由小寫英文字母組成的字符串,請把這個字符串的所有非空後綴按字典序從小到大排序,然後按順序輸出後綴的第一個字符在原串中的位置。位置編號爲 11nn

除此之外爲了進一步證明你確實有給後綴排序的超能力,請另外輸出 n1n - 1 個整數分別表示排序後相鄰後綴的最長公共前綴的長度。

輸入格式
一行一個長度爲 nn 的僅包含小寫英文字母的字符串。

輸出格式
第一行 nn 個整數,第 ii 個整數表示排名爲 ii 的後綴的第一個字符在原串中的位置。

第二行 n1n - 1 個整數,第 ii 個整數表示排名爲 ii 和排名爲 i+1i + 1 的後綴的最長公共前綴的長度。

樣例一
input
ababa

output
5 3 1 4 2
1 3 0 2

explanation
排序後結果爲:

a
aba
ababa
ba
baba
限制與約定
1n1051 \leq n \leq 10^5
時間限制:1s1\texttt{s}
空間限制:256MB256\texttt{MB}

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章