「专题总结」后缀数组

SA能做的事:
1.求lcp、lcs
2.求本质不同的子串数
3.比较子串字典序
4.给定子串求排名
5.给定排名求子串
6.求子串出现次数
7.求多串最长公共子串
以上“子串”意为SA处理的串的子串,可以是输入给出,然后接在后面。


模板

void SA(char *s,int m=26){
	F(i,1,m)c[i]=0;
	F(i,1,n)++c[x[i]=s[i]-'a'+1];
	F(i,1,m)c[i]+=c[i-1];
	F(i,1,n)sa[c[x[i]]--]=i;
	for(reg int len=1,num;len<=n;len<<=1){num=0;
		F(i,n-len+1,n)y[++num]=i;
		F(i,1,n)if(sa[i]>len)y[++num]=sa[i]-len;
		F(i,1,m)c[i]=0;
		F(i,1,n)++c[x[i]];
		F(i,1,m)c[i]+=c[i-1];
		for(reg int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
		F(i,1,n)y[i]=x[i],x[i]=0;
		x[sa[1]]=m=1;
		F(i,2,n)x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+len]==y[sa[i-1]+len])?m:++m;
		if(m==n)break;
	}
	F(i,1,n)rk[sa[i]]=i;
	for(reg int i=1,k=0;i<=n;h[rk[i++]]=k,k-=k>0)
		while(i+k<=n&&sa[rk[i]-1]+k<=n&&s[i+k]==s[sa[rk[i]-1]+k])++k;
}

A. Sandy的卡片

把原串差分,问题转化为求N个串的最长公共子串

trick:多串同时比较,把所有串卡在一起,并用空字符隔开。

公共子串的相似度高,在字典上一定连续,给每个后缀打上原串标记。
问题转化为求包含n个不同标记的区间的lcp的最大值
显然区间缩小答案不会变差,所以用双指针在字典上扫一遍即可。

B. 喵星球上的点名

两个字符串代表一只喵,所以不好统计答案。
把所有串接在一起处理SA。
一只喵看成一种颜色,把其的两个串都染上颜色。
对点名串二分出它作为子串的区间。
那么第一问实质在求一个区间内有多少不同颜色,可以排序,记录pre,然后用BIT优化。同《HH的项链》
第二问求每种颜色被多少区间包含,我不会(nlogn)。莫队比较好做,记录每种颜色进入区间的时间,然后在该颜色全部退出区间时统计存在时间即可。(然后第一问也顺便做了)

C. 字符串

在字典上离得近lcp肯定越优
难点在于有区间限制,考虑主席树维护原串区间rk[i],然后树上查询rk[c]的前趋后继取最优。
以上做法存在一定问题:有可能得出的答案超出了b的限制。考虑二分答案,在二分限制的主席树范围上进行查询。

D. 差异

难点在于求(sumlimits_{1 leq i < j leq n}lcp(T_i,T_j))
所有点对的lcp,那就可以直接在h数组上统计。
考虑(h_i)作为最小值的范围,一定是跨过(i)的一段区间。
单调栈找到左边和右边第一个比(h_i)小的数,注意处理相同元素,方法是一边>=,一边>
也可以直接递归建树,用RMQ快速找到最值。

E. 相似子串

  • 给定子串rank查位置、长度
    把h数组想象成一本字典,从上到下按字典序排序。
    预处理sum数组,(sum[n]=sumlimits_{i=1}^{n}length-sa[i]+1-h[i])
    在sum上二分到子串所在区间(x),长度就是(h[x]+rank-sum[x-1])

  • 求给定位置两子串的lcp、lcs。
    正反求SA,RMQ
    或者像我一样懒,lcs暴力hash

F. 品酒大会

我做法很蠢,启发式合并,还有一层无用循环(nlog^2n)和bit
「r相似」即lcp为r
第一问:用单调栈统计,差分,最后做前缀和得答案
第二问:RMQ预处理区间最大值和最小值,一定是最值相乘。

G.外星联络

求所有本质不同子串的出现顺序,按字典序输出。
处理SA,利用h[]保留信息,优化到N^2

H.跳蚤

一开始的想法是区间划分dp,状态数是NK的,转移可以用三分优化,同时需要主席树。(NKlog^2N)不可做
换个思路,最大值最小,考虑二分子串的排名。不难发现具有单调性。
倒序check,若首字母大则return 0,否则在后边割开,划分次数大于k return 0。
没有想到二分子串排名

I.股市的预测

大神题。求有多少形如ABA的串,B的长度给定
暴力需要枚举A的位置和长度。
枚举A的长度len,把N以len为间隔划分开,每个区间设置“关键点”,发现两端A一定会各自覆盖一个且仅一个“关键点”。
以len为增量枚举位置i,第一个A一定只覆盖i,那么另一端j=i+m+len一定只被第二个A覆盖。
尝试从i,j向两端扩展出区间,即求lcp和lcs,同时都要和len取min,不然一个A覆盖两个关键点,会算重。
若lcp+lcs-(lcp&&lcs)>=len,贡献为 lcp+lcs-(lcp&&lcs)-len+1,len可以在上面滑动。
调和级数。复杂度(nlnn)

J.SvT

(sum t leq 3 imes10^6)所以同样用单调栈求点对lcp,不同在于是在RMQ上做单调栈

原文地址:https://www.cnblogs.com/hzoi-yzh/p/12095840.html