后缀自动机学习笔记及一句话题解

只做了一些水题,所以就先写个比较水的学习笔记(逃)
我来填坑了。。填坑失败了,瞎bb了一些之后,又不知道该写啥

前言

SAM又是最简状态自动机,其状态数是 O ( n ) O(n) O(n)的,从insert函数可以看出来最多是 O ( 2 n ) O(2n) O(2n)的,这个性质很好的保证了一些看似暴力算法的复杂度。所以你可以尽情YY暴力算法

SAM中只有出现的位置集合不同时,才会划分到不同的节点上表示,这个集合我们称 r i g h t right right集合。

parent树上的一个节点的父亲节点其实是相当于这个节点的后缀,这是因为字符串变得更短了,出现的位置可能就更多,因此分裂出去。因此不难发现一个节点表示的子串其实是最长的那个子串开始的某些后缀,且这些后缀的长度连续。所以节点能表示的最短子串长为 l e n [ f [ i ] ] + 1 len[f[i]]+1 len[f[i]]+1

图上一般是用来匹配的,parent树则是失配的时候跳的。也就是把当前匹配到的状态的前面一些给丢了,看能不能后面再接,接到了就更新状态。

比较套路的题目只需要理清楚当前匹配的状态和走到的节点有什么关系就行了。难一点的题目顶多就是在parent树上玩主席树/线段树合并。如果和DP一起考就自求多福吧qwqqq

可能没有了?咕咕咕

求有多少个本质不同的子串

a n s = ∑ i l e n [ i ] − l e n [ f [ i ] ] ans=\sum_i len[i]-len[f[i]] ans=ilen[i]len[f[i]]
结论题,相当于是统计每个节点表示的不同的子串个数。

求拓扑序

对len进行桶排。len其实表示的longest,也就是这个节点所能表示的最长子串的长度。由于在SAM上的从根到某个节点的路径即为一个子串,所以这个也相当于是根走到这个节点的最长路,根据这个性质可以拓扑排序。

for(int i=1;i<=cnt;i++) c[l[i]]++;
for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
for(int i=1;i<=cnt;i++) a[c[l[i]]--]=i;

最小表示法

先复制一遍,加在后面,然后建出SAM,因为SAM上是有所有的子串的,所以就从root开始贪心,往字典序最小的节点走。答案是 l [ x ] − l e n + 1 l[x]-len+1 l[x]len+1,len是原串长。

void dfs(int x,int now)
{
	if(!now){printf("%d\n",l[x]-len+1);return ;}
	for(int i=0;i<26;i++) if(ch[x][i]){dfs(ch[x][i],now-1);break;}
}

LCS

对一个串建出SAM,然后再用另一个串在上面跑匹配。

for(int i=1;i<=n;i++)
{
	if(ch[now][s[i]-'a']) tot++,now=ch[now][s[i]-'a'];
	else
	{
		while(now^1&&!ch[now][s[i]-'a']) now=f[now];
		if(ch[now][s[i]-'a']) tot=l[now]+1,now=ch[now][s[i]-'a'];
		else now=1,tot=0;
	}
	ans=max(ans,tot);
}	

求字典序第k大的子串

即TJOI2015弦论->戳题解

求模板串中循环同构串出现多少次

hihocoder1465
先对模板串建后缀自动机,然后把文本串复制一遍再去掉最后一个字符,跑匹配,如果匹配长度大于原长就跳pre指针,直到原长在点所表示的长度范围内,然后累加其size。注意判重,同一个点不能重复多次计算。

void match()//ori表示原长,len是现在的长度即2*len-1
{
	int x=1,now=0;ans=0;
	for(rg int i=1;i<=len;i++)
	{
		if(ch[x][s[i]-'a']) now++,x=ch[x][s[i]-'a'];
		else
		{
			while(x&&!ch[x][s[i]-'a']) x=pre[x];
			if(x) now=l[x]+1,x=ch[x][s[i]-'a'];
			else x=1,now=0;
		}
		if(now>ori){while(l[pre[x]]>=ori) x=pre[x],now=l[x];}
		if(now>=ori&&vis[x]^dfn) vis[x]=dfn,ans+=sz[x];
	}
	printf("%d\n",ans);
}

快速查找串中子串对应的节点

建出SAM,每次insert之后记录lst,它就是当前前缀所对应的节点,那么当我们要找子串 [ L , R ] [L,R] [L,R]时,其实就是要找前缀 [ 1 , R ] [1,R] [1,R]的长度为 R − L + 1 R-L+1 RL+1的后缀,直接在parent树上倍增即可做到每次询问 O ( log ⁡ n ) O(\log n) O(logn)

你可能感兴趣的:(学习笔记,后缀自动机)