ST04Day4 后缀数组

Further Reading

      罗穗骞《后缀数组——处理字符串的有力工具》

      http://cxjyxx.me/?p=241

      https://oi-wiki.org/string/sa/

      https://blog.xehoth.cc/SA-IS/

      http://riteme.site/blog/2016-6-19/sais.html

正题

      后缀数组大致是怎么做的就不说了,网上很多教程。

      练习题大多在后缀数组上的思想是依据height与子串的刻画,在这里讲讲套路。

      后缀数组:

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

const int N=1000010;
int n,sum[233],rk[N],sa[N],t[N],tmpsa[N];
char s[N];

bool same(int x,int y,int l){
	if(t[x]!=t[y]) return false;
	if((x+l>n)^(y+l>n)) return false;
	if(x+l>n) return true;
	return t[x+l]==t[y+l];
}

void getsa(int m=233,int tot=0){
	for(int i=1;i<=n;i++) rk[i]=s[i],sum[rk[i]]++;
	for(int i=1;i<=m;i++) sum[i]+=sum[i-1];
	for(int i=1;i<=n;i++) sa[sum[rk[i]]--]=i;
	for(int j=1;j<=n;j<<=1){
		tot=0;for(int i=n-j+1;i<=n;i++) tmpsa[++tot]=i;
		for(int i=1;i<=m;i++) sum[i]=0;
		for(int i=1;i<=n;i++) sum[rk[tmpsa[i]]]++,t[i]=rk[i];
		for(int i=1;i<=m;i++) sum[i]+=sum[i-1];
		for(int i=n;i>=1;i--) sa[sum[rk[tmpsa[i]]]--]=tmpsa[i];
		for(int i=1;i<=n;i++) rk[sa[i]]=(i>1&&same(sa[i],sa[i-1],j))?m:++m;
	}
}

int main(){
	scanf("%s",s+1);n=strlen(s+1);
	getsa();
	for(int i=1;i<=n;i++) printf("%d ",sa[i]);
}

      后缀数组的height数组表示的是rk为i后缀和rk为i-1的后缀的lcp长度。具体求法可以令初值为height[rk[i]]=max(height[rk[i]-1]-1,0),然后暴力扩展,正确性考虑将上一个匹配的lcp去掉第一位,复杂度考虑只会下降n次,那么总共只会暴力扩展2n次。

      将所有height[i]>=k的后缀粘在一次

       一般是把height倒着来做,然后维护一个带权并查集,并查集上维护所需信息。

      例题:有一个字符串,你需要找到一个最长的子串,在字符串中出现了两次,且出现的这两次不重叠。

      只需要按照套路,在并查集上维护开头的min和max即可。

      将所有串接在一起,求任意两个后缀的lcp

      我们只需要将所有串接在一起,中间用'$'符号链接,根据中值定理,可以求任意两个串的最长公共子串。

      例题:给定两个字符串,求出最长公共子串的长度。

      这题只需要连在一起,求相邻两个不同所属的height最大值即可,证明考虑中值定理。

      上面两个结合起来就可以这么考:

      相当于求有多少个lcp(a,c)>=l,那么就将height>=l的粘在一起,用并查集维护区间内属于1的个数和属于2的个数。

      子串的刻画

      如何将所有的子串输出?

      后缀排序后,我们只需要从height[i]+1开始输出当前串,这样一定不会重复。

      例题:给定一个字符串,每次给定一个子串,求出有多少个本质不同的子串字典序小于它。

      首先找到该子串在后缀数组中的第一次出现的位置,然后将前面的长度-height加起来就好了,自己的单独算。

      下面就是一堆例题:

      T1

      考虑怎样的一个串是unique子串,对于串进行后缀排序后,求出height,那么以sa[i]开头的unique串的最小右端点为p[i]=max(height[i],height[i+1])。

      那么相当于我们要在[l,r-k+1]中p[i]<=r,然后在其中找最小的rank即可。离线用线段树维护或者直接上树套树即可。

      T2

            同样是按照height从大往小一次合并,然后维护区间内数的个数,记录一下上一次合并的时间戳,在两个并查集合并的时候,用(当前时间-时间戳)*区间后缀个数来更新ans[区间后缀个数] 即可。

      总结

      总结一下,可以知道一些有关lcp的复杂问题可以直接使用后缀数组轻松解决,实际上后缀数组就是后缀树关于叶子的dfs序,所以大部分问题都可以直接放到后缀树上考虑,像上面粘区间的一系列操作,对应到后缀树上实际上就是从下往上合并。

      与后缀自动机比较,还是要多做题才能熟练掌握。

猜你喜欢

转载自blog.csdn.net/Deep_Kevin/article/details/108092443