SAM(后缀自动机)学习小计

43 篇文章 0 订阅
5 篇文章 0 订阅

前言

其实我理解的不是很深刻,有大佬发现错误的请指教

自动机

什么是自动机?
有限状态自动机:能够识别一个字符串
后缀自动机?
能识别一个串的所有后缀(其实可以识别所有子串)

瞎扯和分析

首先,一个状态ST()表示一个字符串。
设要分析的字符串为S,suf为后缀集合
对于属于串S的每一个子串s1,s1需要开一个状态,因为后面加上一些字符后可能成为后缀
先假设对于每个子串s1,开一个状态
我们需要让s1被自动机识别,当且仅当 s1suf
s1可能在S中出现多次,设为[li~ri]
记ST(s1)的right集为ri的集合,即
right(ST(s1))={ri|s1=S[li,ri]}
在知道s1的情况下,可以知道right()
那么在知道状态x和right(x)的情况下,也可以知道它所代表的子串。
对于x,记一个len,表示这个状态表示的子串的长度。
那么x所代表的子串就是 S[rilen+1,ri]riright(x)
目前,我们对于所有子串都记录了一个状态,现在,考虑合并。

right()集是一个很重要的东西,所以合并就把所有right()集相同的状态合成一个。
这样会有所不同,原来一个状态x,记录了len表示子串长度,现在很多状态合起来了,子串长度也变成了区间,记为[min(x),max(x)]。
举个例子:
串S:ABBABB,s1:ABB,s2:BB
那么
right(st(s1))={3,6},len=3
right(st(s2))={3,6},len=2
发现完全一样
那么就合并起来,len变成区间[2,3]

一个性质:合并后两个状态的right集要么不相交,要么一个是另一个的真子集
证明:
假设两个状态a,b,right集相交
由定义知:两个状态代表的子串中没有相同的

[min(a),max(a)]与[min(b),max(b)]无交集
那么假设 max(a)<min(b) ,那么a中所有串比b中短,右端点又相同,那么a中的串都是b中的串的后缀,所以b真包含a,得证

同时可以知道,合并后的状态数量为O(n)

构造一棵树,树上一个点x,父亲为y,满足:
right(x)right(y) 且|right(y)|最小
这棵树叫fail树或parent树
根据right的性质,每个点必有至少两个儿子,叶节点有n个
所以大小是O(n)的
性质:max[y]=min[x]-1
因为一个状态代表的子串的长度有下限,超过下限的是因为有些位置超过了right(x),这些位置必然在y里面

状态合并后,得到的是一个DAG,而一条从起点到终点集中的一个点的路径代表了一个后缀

一个子串会包含在某个状态里,所以SAM也可以识别子串,子串出现个数就是right集大小
right集不能直接记录,在fail树上一个叶子节点的right集大小是1,而right集不是包含就是无交,所以记录下叶子上的就行了

构造

回顾一下:
right集:相同字符串右端点的集合
right集两两包含或无交
状态x是right集相同的子串合并起来,长度是一个区间[min,max]
fail树上max[fa[x]]=min[x]-1

构造的方法是增量法(好像很多字符串有关的东西都是这个)
因为有max[fa[x]]=min[x]-1所以只记录max

目前已经有一个字符串S,要加入一个字符c
设上次加入的状态为last,p=last
新增一个状态np,len(np)=len(p)+1,因为加入了一个字符
沿p的fail链走,走到的点如果没有c这个儿子,就连到np
p走到第一个有c的点,设c那条边连接q
假设max[q]>max[p]+1
会发现如果将fail[np]=q即将新的这个点加入q的right集,会有max[p]+2<=len<=max[q],len是插入的一个串,也就是说这个串在np中已经出现了,不能让一个串出现在两个状态中
而max[q]=max[p]+1时就不会出现,所以这时可以直接fail[np]=q
所以可以设一个nq,right(nq)表示right(q)与right(np)的并集,而max(nq)=max(p)+1,fail(q)=fail(np)=nq,这样加入了一部分新后缀,重复的就不会加入了
(难理解)

代码与题目

void add(int x)
{
    int p=last,np=++tot;
    t[np].len=t[p].len+1;t[np].size=1;last=np;
    for(;p&&t[p].to[x]==0;p=t[p].fail) t[p].to[x]=np;
    if(p==0){t[np].fail=1;return;}
    int q=t[p].to[x];
    if(t[p].len+1==t[q].len) t[np].fail=q;
    else
    {
        int nq=++tot;t[nq]=t[q];
        t[nq].len=t[p].len+1;t[q].fail=t[np].fail=nq;
        for(;p&&t[p].to[x]==q;p=t[p].fail) t[p].to[x]=nq;
    }
}

模板与模板题
【NOI2016模拟3.1】hypocritical
【ZJOI2015】诸神眷顾的幻想乡

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值