后缀数组复习笔记

好久之前就打算写了,结果咕到现在

倍增求 SA

考虑使用类似计数排序的思想来求 SA,对每个位置 \(i\) 有双关键字 \((rk[i],rk[i+w])\) 优先按照第一关键字排序

char s[np];
int n,id[np * 2],bac[np];
int height[np];
int rk[np * 2],sa[np * 2],oldrk[np * 2];
// rk[i] 表示后缀 i 的排名,sa[i] 表示排名为 i 的后缀是谁
// oldrk[i] 表示上一轮 rk
inline void getsa()
{
	int alp=max(256,n);
	for(int i=1;i <= n;i ++) bac[rk[i] = s[i]] ++;
	for(int i=1;i <= alp;i ++) bac[i] += bac[i-1];
	for(int i=n;i >= 1;i --) sa[bac[rk[i]]--] = i;
	for(int w=1;w < n;w <<= 1) // (i,i+2^w)]
	{
		for(int i=0;i <= alp; i++) bac[i] = 0,id[i] = i;
		for(int i=1;i <= n;i ++) bac[rk[id[i] + w]] ++;//排第二关键字
		for(int i=1;i <= alp;i++) bac[i] += bac[i-1];
		for(int i=n;i >= 1;i --) sa[bac[rk[id[i] + w]]--] = id[i];
		
		for(int i=0;i <= alp;i ++) bac[i] = 0,id[i] = sa[i];// 按照上一维的排名排第一维
		for(int i=1;i <= n;i ++) bac[rk[id[i]]] ++;
		for(int i=1;i <= alp;i ++) bac[i] += bac[i-1];
		for(int i=n;i >= 1;i --) sa[bac[rk[id[i]]]--] = id[i];
		
		for(int i=1;i <= n;i ++) oldrk[i] = rk[i];
		for(int i=1,p=0;i <= n;i ++)
		{
			if(oldrk[sa[i]] == oldrk[sa[i - 1]] && oldrk[sa[i] + w] == oldrk[sa[i-1] + w])
			rk[sa[i]] = p;// 本质相同
			else rk[sa[i]] = ++p;// 
		}
	}
 }

求 height 数组

规定:
\(\operatorname{height}_i\) 表示 \(i-1\)\(i\) 的两后缀的最长公共前缀

\[\operatorname{height}_i = \operatorname{lcp}(sa_{i-1},sa_i) \]

\(h_i\) 表示后缀 i 和排名在 i 之前一位的后缀最长公共前缀

\[h_i = \operatorname{height}_{rk_i} = \operatorname{lcp}(sa_{rk_i-1},i) \]

直接给出结论

\[\operatorname{lcp}(sa_i,sa_j) = \min\limits_{k=i+1}^j\{\operatorname{height}_k\} \]

如何求 \(\operatorname{height}\) 数组呢

再给出结论 \(h_i \geq h_{i-1}-1\)

给出证明

对于 \(h_i = \operatorname{lcp}(i,sa_{rk_i-1})\)

\(u = i,v = sa_{rk_i-1}\)

对于 \(h_{i-1}\) 来说,设 \(u' = i-1,v' = sa_{rk_{i-1}-1}\)

不妨设 \(k=h_{i-1}\geq 1\) 对于另一部分结论显然成立

则有 \(\operatorname{lcp}(u',v')=k\),考虑去掉 \(u'\)\(v'\) 的首字母后分别为 \(x\)\(y\),且有 \(x\)\(y\) 的字典序相对大小不变。显然有 \(\operatorname{lcp}(x,y)=k-1\) 并且 \(x=u=i\)。相当于 \(\operatorname{lcp}(i,y)=k-1\)

最后有如下关系 \(y\leq v < i\) 根据 LCP lemma 有 \(\operatorname{lcp}(v,i)\geq \operatorname{lcp}(y,i)\)

证毕

这条性质可以被称作「不完美单调性」

inline void getHeight()
{
	for(int i=1,k=0;i <= n ;i ++)
	{
		if(rk[i] == 1) height[1] = 0;
		else{
			if(k > 0) k --;
			int l=sa[rk[i] - 1];
			while(l + n <= n && i + k <= n && s[i + k] == s[l + k]) k ++;
			height[rk[i]] = k;
		}
	}
}

CF1063F

显然有一个 \(O(n\sqrt n)\) 的优雅做法,但我们要追求 \(\rm polylog\) 做法

考虑一个dp

设 dp[i] 表示起始是 i 的最大划分数量。则有这样一个转移

\[dp[i] = \min_{j>i}(j-i,dp[j]+1,\operatorname{lcp}(s[i:n],s[j:n])) \]

或者

\[dp[i] = \min_{j>i}(j-i,dp[j]+1,\operatorname{lcp}(s[i+1:n],s[j:n])) \]

现在我们手里有了一个 \(O(n^2)\) 的 dp 考虑优化

经过推理可以发现一个性质 \(dp[i] \leq dp[i+1] + 1\)

可以观察出这个东西非常像 \(\rm height\) 的数组形式,我们考虑和它类似的维护

每次都直接继承上一个答案,然后顺序递减枚举即可。

考虑如何 check 当前 dp 值是否合法

相当于 dp 值需要同时满足 min 中的三个表达式

对于 \(\operatorname{lcp}(s[i+1:n],s[j:n])\) 只需要在对应的 \(sa\) 区间上二分左右端点就行。

对于前两个条件,只需要保证在当前 dp 值下降的时候维护一棵线段树不断向其中插入元素即可,同时维护 dp 最大值。

link

P4094

sa 套路题之一吧算是。

首先二分答案

考虑如何 check 是否存在 \(\max\limits_{i=a}^b\operatorname{lcp}(s[i:b],s[c:d]) \geq ans\) 即可

首先对 sa 建主席树,每次 check 直接在 c 在 sa 的位置上二分左右区间,然后做一个二维数点就行

sa 经典题了算是

link

P4770

首先有一个和上题一模一样的做法不过复杂度是 \(O(\sum|T|\log^2n)\)

考虑优化,发现对于 \(T[i-1]\)\(T[i]\) 它们必然有这样一个关系

\[\operatorname{lcp}(S[l:r],T[i-1])-1\leq\operatorname{lcp}(S[l:r],T[i]) \]

考虑和 \(h_i\) 一样的证明即可,对于不完全单调性可以使用一个类似 CF1063F 的做法,这样复杂度就被降到 \(O(\sum|T|\log n)\)

但是由于每次都要在主席树上查询和二分实在是太慢了,并且数组访问并不连续。

原文地址:https://www.cnblogs.com/-Iris-/p/15557232.html