【数据结构】后缀自动机(SAM)小记

后缀自动机(SAM)小记

介绍

简单来说,就是使用一个 DAG 以及一棵树维护一个字符串所有子串(压缩的)信息。

其中 DAG 的点称为状态

endpos

个人认为 SAM 的核心在于 endpos

子串(终点)在原串出现的下标集合称为 endpos 集合。

例如,对于原串 aabaabaendpos(ab)={3,6}

性质

endpos 有如下性质:

  • 对于任意子串 a,b,二者 endpos 的关系必为:

    1. endpos(a)endpos(b)

    2. endpos(a)endpos(b)=

    之一

  • endpos 相同的两个子串属于一个等价类。(这意味着:SAM 中每个状态会对应 endpos 相同的若干个子串)

  • 原串中 endpos 对应的等价类数量级为 O(N)

  • 对于一个状态 st 及任意的 longest(st) 的后缀 s ,若有:|shortest(st)||s||longsest(st)|,则 ssubstrings(st)

    |shortest(st)|minlen(st)|longest(st)|len(st)

由上面的 endpos 性质二可知,每个 endpos 集合可以被划分为若干个集合(每个集合当然可以同样地划分下去),这样我们就可以按照划分的过程得到一个树形的结构,这被称为 link

对于一个状态 st,我们有:minlen(st)=len(link(st))+1

后缀自动机的建立

鉴于会涉及代码,先放模板题链接:

https://www.luogu.com.cn/problem/P3804

这是一个在线的过程,就是按照每次加一个点(对应字符串中加一个字符)然后相应地连边构造的。

建立过程见代码注释部分。

每个状态维护了:

  • link 树的父节点 fa
  • 该状态对应的等价类中最长的子串长 len
  • DAG 中连接的字符集所到的状态 ch[]

性质

  • 起点开始沿蓝边任意路径所到的状态为该状态的子串。
  • fa(st) 的任意子串一定是 st 任意子串的后缀

更多性质见下图:

(这是 aabbabd 的后缀自动机)

image

参考代码

树上计数。

#include<bits/stdc++.h>
using namespace std;

const int N=2e6+5, M=N<<1;

#define int long long

struct Edge{
	int to, next;
}e[M];

int h[N], idx;

void add(int u, int v){
	e[idx].to=v, e[idx].next=h[u], h[u]=idx++;
}

char str[N];

struct Node{
	int len, fa; // longest length of state, link
	int ch[26];
}node[N];
int tot=1, last=1; // 初始的根
int f[N];

void ins(int c){
	int p=last, np=last=++tot; // np 代表新插入的点(状态)
	f[tot]=1; // 新状态的大小(即子串个数)
	node[np].len=node[p].len+1; // 新插入字符后对应的最长子串当然比前一个状态的长 1。
	for(; p && !node[p].ch[c]; p=node[p].fa) node[p].ch[c]=np; // 跳 link,没有 c 儿子的状态连边。
	if(!p) node[np].fa=1; // 跳到根则将 np 的 link 指向根(因为 c 没有出现过)
	else{
		int q=node[p].ch[c]; // q 为第一个有 c 儿子的状态的 c 儿子
		if(node[q].len==node[p].len+1) node[np].fa=q; // 找到正好接受新串后缀的状态
		else{
			int nq=++tot; // 开新点
			node[nq]=node[q], node[nq].len=node[p].len+1;
			node[q].fa=node[np].fa=nq;
			for(; p && node[p].ch[c]==q; p=node[p].fa) node[p].ch[c]=nq;
		}
	}
}

int res=0;

void dfs(int u){
	for(int i=h[u]; ~i; i=e[i].next){
		int go=e[i].to;
		dfs(go);
		f[u]+=f[go];
	}
	if(f[u]>1) res=max(res, f[u]*node[u].len);
}

signed main(){
	scanf("%s", str);
	for(int i=0; str[i]; i++) ins(str[i]-'a');
	memset(h, -1, sizeof h);
	for(int i=2; i<=tot; i++) add(node[i].fa, i);
	dfs(1);
	
	cout<<res<<endl;
		
	return 0;
}
posted @   HinanawiTenshi  阅读(75)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示