【题解】后缀数组

题目链接

题目描述凑字数

题目描述

后缀数组 (\(SA\)) 是一种重要的数据结构,通常使用倍增或者 \(DC3\) 算法实现,这超出了我们的讨论范围。在本题中,我们希望使用快排、\(Hash\) 与二分实现一个简单的 \(O(n \ log^2⁡n )\) 的后缀数组求法。详细地说,给定一个长度为 \(n\) 的字符串 \(S\)(下标 \(0\)~\(n-1\)),我们可以用整数 \(k(0\le k<n)\) 表示字符串S的后缀 \(S(k\)~\(n-1)\)。把字符串S的所有后缀按照字典序排列,排名为 \(i\) 的后缀记为 \(SA[i]\)。额外地,我们考虑排名为 \(i\) 的后缀与排名为 \(i-1\) 的后缀,把二者的最长公共前缀的长度记为 \(Height[i]\)。我们的任务就是求出 \(SA\)\(Height\) 这两个数组。

输入格式

一个字符串,长度不超过30万。

输出格式

第一行为数组 \(SA\),相邻两个整数用1个空格隔开。

第二行为数组 \(Height\),相邻两个整数用1个空格隔开,特别地,假设 \(Height[1]=0\)

样例

样例输入

ponoiiipoi

样例输出

9 4 5 6 2 8 3 1 7 0
0 1 2 1 0 0 2 1 0 2

数据范围与提示

样例解释

排名第一(最小)的后缀是 \(9\)\(S[9\)~\(9]\),即字符串 \(i\)),第二的是后缀 \(4\)(\(S[4\)~\(9]\),即字符串 \(iiipoi\)),第三的是后缀 \(5\)\(S[5\)~\(9]\),即字符串 \(iipoi\))以此类推。\(Height[2]\) 表示排名第 \(2\) 与第 \(1\) 的后缀的最长公共前缀,长度为 \(1\)\(Height[3]\) 表示排名第 \(3\) 与第 \(2\) 的后缀的最长公共前缀,长度为 \(2\) ,以此类推。

Solution

这不就一道 裸的 字符串Hash嘛。

\(s[x]\)\(s[y]\) 的最长公共前缀进行二分,其中 \(s\) 为输入的字符串。

Q:不是求后缀吗,怎么就变成前缀了?

A:是按后缀字典序排序,对于两个串的字典序排序,若两个串的首字符相同,忽略该字符,比较下一个字符,直到两个串首字符不同或其中一个串为空为止。

二分时会用到 \(Hash\) :比较 \(s[x]\)~\(s[x+mid]\)\(s[y]\)~\(s[y+mid]\) 是否为同一个前缀。其中 \(x\)\(y\)\(\operatorname{cmp}\) 提供的参数,也就是需要进行比较的字符串首位下标。

此时的二分求的是两个串的最长公共前缀长度,在求 \(Height\) 时也可以用到。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using std::sort;
typedef unsigned long long ull;
const int maxn=1e6+5;
const int p=13331;
int len;
char s[maxn];
int sa[maxn];
ull hash[maxn],q[maxn];
ull Hash(int l,int r){
	return hash[r]-hash[l-1]*q[r-l+1];
}
int max(int x,int y){
	return x>y?x:y;
}
int binary_answer(int x,int y){
	if(s[x]!=s[y])return 0;
	int l=0,r=len-1-max(x,y),mid;
	while(l<r){
		mid=l+r+1>>1;
		if(Hash(x,x+mid)==Hash(y,y+mid)&&x+mid<len&&y+mid<len)
			l=mid;
		else r=mid-1;
	}
	return l+1;
}
bool cmp(int x,int y){
	int l=binary_answer(x,y);
	return s[x+l]<s[y+l];
}
int main(){
	scanf("%s",s);
	len=strlen(s);
	q[0]=1;
	hash[0]=s[0]-'a'+1;
	for(int i=1;i<len;++i){
		sa[i]=i;
		hash[i]=hash[i-1]*p+s[i]-'a'+1; 
		q[i]=q[i-1]*p;
	}
	sort(sa,sa+len,cmp);
	for(int i=0;i<len;++i)
		printf("%d ",sa[i]);
	printf("\n0 ");
	for(int i=1;i<len;++i)
		printf("%d ",binary_answer(sa[i],sa[i-1]));
	return 0;
}
posted @ 2020-11-13 20:48  XSC062  阅读(75)  评论(0编辑  收藏  举报