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

后缀自动机(SAM)小记

介绍

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

其中 \(DAG\) 的点称为状态

endpos

个人认为 \(SAM\) 的核心在于 \(endpos\)

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

例如,对于原串 \(\texttt{aabaaba}\)\(endpos(\texttt{ab}) = \{3, 6\}\)

性质

\(endpos\) 有如下性质:

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

    1. \(endpos(a) \subseteq endpos(b)\)

    2. \(endpos(a)\cap endpos(b) = \empty\)

    之一

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

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

  • 对于一个状态 \(st\) 及任意的 \(longest(st)\) 的后缀 \(s\) ,若有:$|shortest(st)|≤|s|≤|longsest(st)| $,则 \(s\in substrings(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\) 任意子串的后缀

更多性质见下图:

(这是 \(\texttt{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 @ 2022-06-24 14:20  HinanawiTenshi  阅读(69)  评论(0编辑  收藏  举报