灵魂滚烫, 命运冰凉|

fhq_treap

园龄:5年7个月粉丝:67关注:21

[学习笔记]后缀相关算法

SA

SA实际上求出两个数组sa,rk

sai表示将所有后缀排序后排名第i小的后缀的编号,rki表示后缀i的排名。

满足其性质sarki=rksai=i

这里仅给出一个O(nlog2n)的做法。

其他做法参见oiwiki

点击查看代码
bool cmp(int i,int j){
	if(rk[i]!=rk[j]){
		return rk[i]<rk[j];
	}else{
		int ri,rj;
		ri=i+k<n?rk[i+k]:-1;
		rj=j+k<n?rk[j+k]:-1;
		return ri<rj;
	}
}

void calc(){
	for(int i=0;i<n;i++){
		rk[i]=s[i];
		sa[i]=i;
	}
	for(k=1;k<n;k*=2){
		sort(sa,sa+n,cmp);
		tmp[sa[0]]=0;
		for(int i=0;i<n-1;i++){
			tmp[sa[i+1]]=tmp[sa[i]]+cmp(sa[i],sa[i+1]);
		}
		for(int i=0;i<n;i++){
			rk[i]=tmp[i];
		}
	}
}

很多情况下我们都要求出辅助数组height数组。

heighti=lcp(sai,sai1)

其有引理heightrki1+1heightrki

所以根据该引理,维护一个指针即可O(n)求出其。

点击查看代码

for (i = 1, k = 0; i <= n; ++i) {
  if (rk[i] == 0) continue;
  if (k) --k;
  while (s[i + k] == s[sa[rk[i] - 1] + k]) ++k;
  height[rk[i]] = k;
}

其应用大概有:

两个子串最长公共前缀

lcp(sai,saj)=minheaighti+1...j

不同子串数目

n(n+1))2i=2nheighti

连续的相同子串([NOI2016] 优秀的拆分):

考虑枚举长度然后设置关键点。
若存在AA,则AA一定跨越两个关键点,枚举两关键点,其一定有[1,x][1,y]的后缀最长公共子串加[x,n][y,n]的前缀最长公共子串大于枚举长度。
配合图食用。
image

搭配其他数据结构

略。

SAM

SAM是一个接受字符串S的所有后缀的最小DFA。
其也是对于将所有子串插入trie里的trie图压缩的结果。

子串的性质

S的子串和从t0出发的任意路径对应。

一些概念和性质

结束位置 endpos

考虑字符串的任意非空子串,我们记endpos(t)为其所有的结束位置。

考虑按endpos(t)分成若干等价类。

显然,SAM的每个状态对应等价类。

引理1:字串两个子串为u,w,其endpos相同,长度小的总为长度长的后缀。

引理2:每个等价类的元素子串的长度连续,且后缀关系传递。

后缀链接 link

考虑SAM中的某个不是t0的状态v,我们已经知道,状态v其对应子串均为其代表等价类的最长串(T)的后缀,我们设S=suffix(T),其等价类v的后缀链接连向maxlen(S)(endpos(S)endpos(T))对应S的等价类。

我们设endpos(t0)=1,0,....,|S|1

引理3:所有后缀链接构成一颗根节点为t0的树,其上子节点的endpos是父节点的endpos的子集。

后缀自动机建法

考虑增量建法。

考虑已经维护了S的后缀自动机,现在要加入一个字符c,考虑如何维护:

len为其对应等价类里的最长长度。

带图的建树过程

点击查看代码
ll nod = 1,lst = 1;

inline void insert(int c){
	int p = lst,q = ++nod;lst = q;
	len[q] = len[p] + 1,f[q] = 1;
	while(!ch[p][c] && p != 0){//向上找 
		ch[p][c] = q;
		p = link[p];
	} 
	if(p == 0)
	link[q] = 1;
	else{
		int x = ch[p][c];
		if(len[p] + 1 == len[x]){
			link[q] = x;
		}else{
			int y = ++ nod ;//复制一个新节点
			link[y] = link[x];
			link[x] = link[q] = y;
			len[y] = len[p] + 1;
			std::memcpy(ch[y],ch[x],sizeof(ch[x])); 
			while(p != 0 && ch[p][c] == x){
				ch[p][c] = y;
				p = link[p];
			}
		}
	}
}

本文作者:fhq_treap

本文链接:https://www.cnblogs.com/dixiao/p/15936062.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   fhq_treap  阅读(76)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
历史上的今天:
2021-02-25 CF618F Double Knapsack
2021-02-25 【BZOJ 4668 冷战】
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起