后缀自动机

一个字符串的后缀自动机

一些定义

符号意义 D A G DAG DAG上的含义
C C C原字符串
t r a n s ( S , c ) trans(S,c) trans(S,c)状态转移函数,表示 S S S状态在读入字符 c c c后到达的状态一条以 S S S为起点,边权为 c c c的边
t r a n s ( S , s ) trans(S,s) trans(S,s)状态转移函数,表示 S S S状态在读入字符串 s s s后到达的状态一条以 S S S为起点,经过的所有边权依次为 s s s的路径
r i g h t s { } right_s\{\} rights{} s s s C C C中所有出现位置末尾的下一个位置
r e g S { } reg_S\{\} regS{} r i g h t { } right\{\} right{}相同的所有子串的集合,状态为 S S S
m i n S min_S minS min ⁡ S ∈ r e g S { } { l e n t h ( s ) } \min_{S\in reg_S\{\}}\{lenth(s)\} minSregS{}{lenth(s)}
m a x S max_S maxS max ⁡ S ∈ r e g S { } { l e n t h ( s ) } \max_{S\in reg_S\{\}}\{lenth(s)\} maxSregS{}{lenth(s)}
p r a e n t S praent_S praentS r e g p r a e n t S { } ⊂ r e g S { } reg_{praent_S}\{\}\subset reg_S\{\} regpraentS{}regS{} r e g { } reg\{\} reg{}大小最小所有 p a r e n t parent parent构成一颗 p a r e n t parent parent

一些性质

m i n s = m a x p a r e n t S + 1 min_s=max_{parent_S}+1 mins=maxparentS+1

一个状态的 r i g h t { } right\{\} right{}是其在 p a r e n t parent parent树上父节点的 r i g h t { } right\{\} right{}集合的真子集,是其在 p a r e n t parent parent树上子节点的 r i g h t { } right\{\} right{}的并集

如何构造一个 S A M SAM SAM

增量算法

考虑已经建出 C [ 1 , i − 1 ] C[1,i-1] C[1,i1] S A M SAM SAM,求 C [ 1 , i ] C[1,i] C[1,i] S A M SAM SAM

设在最后添加的字符 x = C [ i ] x=C[i] x=C[i]

考虑状态 S S S,满足 t r a n s ( S , x ) ! = n u l l trans(S,x)!=null trans(S,x)!=null,
即存在一个 r ∈ r i g h t S { } r\in right_S\{\} rrightS{}满足 C [ r ] = x C[r]=x C[r]=x,
显然 t r a n s ( p r a e n t S , x ) ! = n u l l trans(praent_S,x)!=null trans(praentS,x)!=null,即 S S S p a r e n t parent parent树根的所有结点都存在出边 x x x

记录上一个插入的节点 l a s t last last,新建节点 n p np np表示当前添加的字符

p p p l a s t last last到根路径上第一个有出边 x x x的节点,将路径上在 p p p下方的节点都连一条出边 x x x指向 n p np np

若初始状态没有出边 x x x,说明 l a s t last last到根路径上的点都不存在出边 x x x,将 p a r e n t n p parent_np parentnp设为初始状态

否则,设 l e n S = m a x S len_S=max_S lenS=maxS, q = t r a n s ( p , x ) q=trans(p,x) q=trans(p,x)

l e n q = l e n p + 1 len_q=len_p+1 lenq=lenp+1,将 p a r e n t n p parent_{np} parentnp设为 q q q

否则,新建状态 n q nq nq, l e n n q = l e n p + 1 len_{nq}=len_p+1 lennq=lenp+1, t r a n s ( n q , ∗ ) = t r a n s ( q , ∗ ) trans(nq,*)=trans(q,*) trans(nq,)=trans(q,)

p a r e n t n q parent_{nq} parentnq设为 p a r e n t q parent_{q} parentq

再将 p a r e n t q parent_{q} parentq p a r e n t n p parent_{np} parentnp设为 n q nq nq

最后将 p p p及其祖先的出边 x x x指向 n q nq nq

至于为什么这么做,可以结合样例思考一下

Null

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YgvKLnLI-1598857277428)(https://00ffcc.cf/images/SAM/Null.png)]

a

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-09ajHBGq-1598857277433)(https://00ffcc.cf/images/SAM/a.png)]

ab

aba

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-63l6g6LZ-1598857277445)(https://00ffcc.cf/images/SAM/aba.png)]

abab

ababb

应用

统计子串个数

模板题

#include<bits/stdc++.h>
using namespace std;
#define Ri register
template<typename T>inline T read(Ri T& t)
{Ri T f=1;Ri char ch=getchar();t=0;
while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
while(ch>='0'&&ch<='9')t=t*10+ch-'0',ch=getchar();t*=f;return t;}
template <typename T,typename...Args>
inline void read(T&t,Args&...args)
{read(t);read(args...);}
int total=1;
int sam[2000006][26];
char S[2000006];
long long cnt[2000006],len[2000006];
int parent[2000006];
int last=1;
void Insert(char c)
{
	int x=c-'a';
	int p=last;
	int np=++total;last=np;
	cnt[np]=1;len[np]=len[p]+1;
	while(sam[p][x]==0&&p)
		sam[p][x]=np,p=parent[p];
	if(p==0)return parent[np]=1,void();
	int q=sam[p][x];
	if(len[q]==len[p]+1)return parent[np]=q,void();
	int nq=++total;
	memcpy(sam[nq],sam[q],sizeof(sam[q]));
	parent[nq]=parent[q];
	parent[q]=parent[np]=nq;
	len[nq]=len[p]+1;
	while(sam[p][x]==q&&p)
		sam[p][x]=nq,p=parent[p];
}
int sum[2000006];//某长度的节点个数 
int main()
{
	scanf("%s",S+1);
	int n=strlen(S+1);
	for(int i=1;i<=n;i++)
		Insert(S[i]);
	for(int i=1;i<=total;i++)
		sum[len[i]]++;
	for(int i=1;i<=total;i++)
		sum[i]+=sum[i-1];
	static int opt[2000006];
	for(int i=1;i<=total;i++)
		opt[sum[len[i]]--]=i;
	long long ans=0;
	for(int i=total;i>=1;i--)
	{
		cnt[parent[opt[i]]]+=cnt[opt[i]];
		if(cnt[opt[i]]>1)
			ans=max(ans,len[opt[i]]*cnt[opt[i]]);
	}
	printf("%lld\n",ans);
	return 0;
}

动态维护本质不同子串个数

link

#include<bits/stdc++.h>
using namespace std;
#define Ri register
template<typename T>inline T read(Ri T& t)
{Ri T f=1;Ri char ch=getchar();t=0;
while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
while(ch>='0'&&ch<='9')t=t*10+ch-'0',ch=getchar();t*=f;return t;}
template <typename T,typename...Args>
inline void read(T&t,Args&...args)
{read(t);read(args...);}
map<int,int> sam[200005];
int total=1;
int last=1;
int len[200005];
int parent[200005];
long long ans=0;
inline void Insert(int x)
{
	int np=++total;
	int p=last;last=np;len[np]=len[p]+1;
	while(sam[p].count(x)==0&&p)
		sam[p][x]=np,p=parent[p];
	if(p==0)return parent[np]=1,ans+=len[last]-len[parent[last]],void();
	int q=sam[p][x];
	if(len[q]==len[p]+1)return parent[np]=q,ans+=len[last]-len[parent[last]],void();
	int nq=++total;
	len[nq]=len[p]+1;
	sam[nq]=sam[q];
	parent[nq]=parent[q];
	parent[q]=parent[np]=nq;
	while(p&&sam[p][x]==q)
		sam[p][x]=nq,p=parent[p];
	ans+=len[last]-len[parent[last]];
//	printf("%d %d\n",len[last],len[parent[last]]);
}
int main()
{
	int n;
	read(n);
	while(n--)
	{
		int x;read(x);
		Insert(x);
		printf("%lld\n",ans);
	}
	return 0;
}
nt[last]];
//	printf("%d %d\n",len[last],len[parent[last]]);
}
int main()
{
	int n;
	read(n);
	while(n--)
	{
		int x;read(x);
		Insert(x);
		printf("%lld\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值