后缀数组

后缀排序

char s[N];
int n,sa[N],rk[N],ork[N<<1];
int buc[N],id[N],pid[N];

bool cmp(int a,int b,int w){return ork[a]==ork[b] && ork[a+w]==ork[b+w];}

void build()
{
	int m=(1<<17),p=0;
	for(int i=1; i<=n; i++) buc[rk[i]=s[i]]++;
	for(int i=1; i<=m; i++) buc[i]+=buc[i-1];
	for(int i=n; i>=1; i--) sa[buc[s[i]]--]=i;
	for(int w=1; ; w<<=1,m=p,p=0)
	{
		for(int i=n; i>n-w; i--) id[++p]=i;
		for(int i=1; i<=n; i++) if(sa[i]>w) id[++p]=sa[i]-w;
		for(int i=0; i<=m+1; i++) buc[i]=0;
		for(int i=1; i<=n; i++) buc[pid[i]=rk[id[i]]]++;
		for(int i=1; i<=m; i++) buc[i]+=buc[i-1];
		for(int i=n; i>=1; i--) sa[buc[pid[i]]--]=id[i];
		for(int i=1; i<=n; i++) ork[i]=rk[i];
		p=0;
		for(int i=1; i<=n; i++) rk[sa[i]]=cmp(sa[i-1],sa[i],w)? p:++p;
		if(p==n) break;
	}
}

h 数组

关键性质:hrkihrki11

int k=0;
for(int i=1; i<=tot; i++)
{
	if(k) k--;
	while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
	h[rk[i]]=k;
}

应用

1、求任意两个后缀的 LCP

lcp(i,j)=minp=min(rki,rkj)+1max(rki,rkj)hp

2、求本质不同的子串个数

n(n+1)2i=2nhi

3、与单调栈结合

h 数组倒过来可以得到一个柱状图,这是用单调栈解决问题的基础。以 P4248 [AHOI2013] 差异 为例,求两两后缀 lcp 之和。

考虑按 rk 从小到大加入 h(其实就是依次加入 h1n),设 F(i)=p=1i1|lcp(sai,sap)|=p=1i1minq=p+1ihq,答案即 F(i)。用单调栈拆掉那个 min,变成维护单调栈矩形面积和。这是我们熟悉的问题。

一些题目

CF822E Liar

考虑朴素 DP,设 fi,j 表示匹配到 s 的第 i 位,t 的第 j 位的最小次数,转移即考虑 s[i+1:]t[j+1:] 的最长公共前缀。考虑优化,注意到 x30,所以值域定义域互换,设 fi,a 表示用了 si 位,a 个子串,最远能匹配到 t 的哪一位。转移不表。

P2178 [NOI2015] 品酒大会

因为是 r 相似也就一定是 0r1 相似,所以从大到小考虑。与 lcp 有关的是夹在这两个后缀排名间 himin,考虑并查集,每次将 hi=r 的后缀 saisai1 合并,那么只需维护联通块大小,最大值、次大值、最小值、次小值即可。

P5341 [TJOI2019] 甲苯先生和大中锋的字符串

使用 SA 分析字符串的常见方式:将字符串按照 rk 排序。

我们在排序后的字符串取出一个长度为 k 的区间 [i,i+k1],显然这个区间的长度上界为 lcpj=ii+k1sj。下界就是 max(hi+k,hi)。单调队列即可 O(n)

posted @   xishanmeigao  阅读(5)  评论(0编辑  收藏  举报
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
历史上的今天:
2023-08-25 博弈论小记
2023-08-25 欧拉路径回顾
2023-08-25 2023.8.25测试
点击右上角即可分享
微信分享提示