后缀自动机

BZOJ3277 串(后缀自动机)

試著忘記壹切 提交于 2020-04-06 00:27:08
  对多串建立SAM的一种方法是加分隔符。于是加完分隔符建出SAM。   考虑统计出每个节点被多少个串包含。让每个串各自在SAM上跑,跑到一个节点就标记(显然一定会完全匹配该节点,因为是对包含其的串建的SAM)并暴跳fail,遇到已经被该串标记过的点就停止。   这样暴力的复杂度容易感性证明是O(Lsqrt(L))(L即所有串总长度),因为暴力一个串的过程中,SAM每个点至多被标记一次,每一步跳fail的次数也显然不会超过该串长度,于是对该串的复杂度是min(L,|S| 2 )(S即该串长度),总的最劣复杂度大约就是sqrt(L)个长度为sqrt(L)的串时取得,且常数极小。当然也可以使用树剖实现,不用分析复杂度就能知道是O(Llog 2 L)。   然后递推求出每个点被经过时的具体贡献,也即其到parent树的根的路径上所有出现在至少k个串中的点的len-lenfa值的和。再对每个串各自跑一遍累加所经过点的贡献即可。 #include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> #include<vector> using namespace std; #define ll long long #define N 400010

后缀自动机SAM学习

走远了吗. 提交于 2020-04-04 19:01:21
最近学完了SAM但是一直没有动手实现,先存一下代码,明天再写笔记(偷懒)。 学习自 struct SAM { int maxlen[N],trans[N][26],link[N],tot,last; inline void extend(int id) { int cur=++tot,p; maxlen[cur]=maxlen[last]+1; for(p=last;p&&!trans[p][id];p=link[p]) trans[p][id]=cur; if(!p) link[cur]=1; else { int x=trans[p][id]; if(maxlen[x]==maxlen[p]+1) link[cur]=x; else { int y=++tot; maxlen[y]=maxlen[p]+1; for(int i=0;i<26;i++) trans[y][i]=trans[x][i]; link[y]=link[x]; for(;p&&trans[p][id]==x;p=link[p]) trans[p][id]=y; link[cur]=link[x]=y; } } last=cur; } }sam; 来源: https://www.cnblogs.com/Suiyue-Li/p/12633177.html

后缀自动机专题

一笑奈何 提交于 2020-02-17 15:49:10
如果不算pre指针的话后缀自动机就是一个DAG,这是它能很方便地进行dp的前提。 而pre指针返回什么呢,返回的就是上一个的前缀包含改结点所代表子串的那个后缀,和AC自动机上的fail指针很像,都是为了匹配。我目前学得不深,看不出和AC自动机的fail指针有什么区别,用起来也几乎一样。 相比于字典树和回文树,后缀自动机每个结点会有多个父结点,可以表示多种子串(从根节点到它的每条路径都是一个子串),因此子串的信息只能在路径中记录,比如长度,而该子串说记录的长度step,则是根结点到它的最远距离,而某个子串的长度就是该代表该子串的路径的长度了,并不一定是某个结点的step。 spoj1811 求两个串的最长公共子串的长度。 对A串建立后缀自动机,对B串进行匹配,如果匹配失败,沿着失败指针往回走到第一个能匹配的位置继续匹配(看起来似曾相识?没错,这不是AC自动机的过程吗。。。),当然如果到根节点还不能继续匹配,那就只有从头再来了。这里随时记录长度更新答案即可。 当然后缀数组也可以做,把两个串拼接起来,求lcp即可。。。然后后缀自动机好快。。。。在spoj上居然60ms过了。。。 #include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a))

AC自动机&amp;后缀自动机

匿名 (未验证) 提交于 2019-12-02 23:47:01
理解的不够深 故只能以此来加深理解 。我这个人就是蠢没办法 学长讲的题全程蒙蔽。可能我字符串就是菜吧,哦不我这个人就是菜吧。 AC自动机的名字 AC 取自一个大牛 而自动机就比较有讲究了 不是寻常的东西呢。 自动机由5部分组成 1 字符集 2 状态集合 3 初始状态 4 结束状态集合 5 状态转移函数。 字符集 是指自动机字符的集合。 当然以上有点深奥,我们只需要其能识别字符串即可。 显然的是 KMP做单字符串对单字符串的匹配使用 而AC自动机则是多个字符串在一个字符串上的匹配。 构建trie 大家都会 但是如何求fail 指针呢 思考一下我们求的fail指针其实是指向和当前这个串能匹配的最长后缀。前缀是没有必要的因为我是利用答案串进行匹配的,因为我们得到一个最长前缀再继续这样匹配还是一样效果我们既然答案串都匹配到了现在 也就是当前局面已经不可逆了我们只能不断的跳 跳到一个合法地方让其能继续匹配下去 所以跳到一个最长后缀的位置这样如果还失配的话我们还有机会继续跳 如果跳到一个较短的后缀上的话我们把那些长的都扔了这显然是非常不好做法。 那么现在就有了基础思路构造fail指针 然后失配就不断跳 就行了。在构造fail指针的时候我们是先构造了一颗trie树在trie树搞fail指针显然最长后缀<当前匹配的位置 至于那些比当前位置还要深的位置一定不可能出现,那么就是看深度咯

P3804 【模板】后缀自动机

人盡茶涼 提交于 2019-12-02 06:33:51
P3804 【模板】后缀自动机 1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn = 2e6+5; 4 typedef long long ll; 5 char s[maxn]; 6 int a[maxn], c[maxn], size[maxn], n; 7 ll ans = 0; 8 struct SuffixAutoMaton { 9 int last, cnt; 10 int ch[maxn*2][26], fa[maxn*2], len[maxn*2]; 11 void ins(int c) { 12 int p = last, np = ++cnt; 13 last = np; len[np] = len[p]+1; 14 for (;p && !ch[p][c]; p = fa[p]) ch[p][c] = np; 15 if (!p) fa[np] = 1; 16 else { 17 int q = ch[p][c]; 18 if (len[p]+1 == len[q]) fa[np] = q; 19 else { 20 int nq = ++cnt; 21 len[nq] = len[p]+1; 22 memcpy(ch[nq],ch[q],sizeof(ch[q])); 23

[后缀自动机]SAM的一点理解

怎甘沉沦 提交于 2019-12-01 10:10:30
(啊,图片好像还有CSDN水印呢) 主要参考资料:CLJppt。 预备知识 自动机组成:状态、初始状态、终止状态、状态转移、字符集。 什么是状态? 经典图片: ACADD对应的SAM 对于整个串而言,初始状态(以下简称为init)为ROOT,终止状态 集合 (以下简称end)为最上方及最右方的那两个写着D的 圈 (状态既不是字符,也不是子串,在这里把它理解为某个下标更好),所有的状态就是那七个圈,每条实线边代表从一个状态向另一个状态的 状态转移 。字符集不太重要,在这里为26个字母(暂不区分大小写)。 定义的标注 为便于阅读及写作,下文中,定义均使用 定义 的格式进行标注。当发现有问题时,不妨回到开头再看一眼 定义 。 正文 Part 1 自动机&&后缀自动机的一些定义 用 \(trans()\) 代表状态转移函数(即上文所说边)。 \(trans(s,ch)\) 表示从状态s起,通过字符ch转移所到达的状态(就是走标号为ch的边到达的点)。 \(trans(s,ch)\) 表示的状态若不存在,则值为NULL。同时规定 \(trans(NULL,any\underline{\;}possible\underline{\;}value)\) 只能为NULL。 例如, 上图 中,如果当前状态在左下角的A这个 圈 ,通过'D'可以转移到右下角倒数第二个D这个 圈 ,通过'C'可以转移到。

后缀自动机学习小记

别说谁变了你拦得住时间么 提交于 2019-11-29 15:06:22
前言 实话是,我不知道自己学了多少遍了……感觉总有些模糊,于是写篇笔记总结一下。 后缀自动机是干啥的? 首先需要知道“自动机”的概念: 有限状态自动机的功能是识别字符串。 令一个自动机 \(A\) ,若它能识别字符串 \(S\) ,则令 \(A(S)=True\) ,否则 \(A(S)=False\) 自动机由五个部分组成—— \(alpha\) :字符集, \(state\) :状态集合, \(init\) :初始状态, \(end\) :结束状态集合, \(trans\) :状态转移函数。 而字符串 \(S\) 的 "后缀自动机" 的作用就是 识别 \(S\) 的所有子串 。 这样有什么用呢? 可以掌握 \(S\) 的子串间的一些关系,从而解决问题,如“本质不同的子串数量”、“字符串的最小表示”等等。 而且由于后缀自动机有一些特殊性质,还可解决许多特殊问题。 更具体的,后缀自动机是干啥的? 在一个有向无环图( \(DAG\) ) 上表示出 \(S\) 的所有子串。 这个 \(DAG\) 有一些性质: 有一个源点,若干个汇点,若干个普通节点。每条从一个节点指向另一节点的有向边上有一个字母。 从源点走到任一个汇点的任一条道路上的所有字母依次连起来,形成了 \(S\) 的一个后缀。 从源点走到任一个节点的任一条道路上的所有字母依次连起来,形成了 \(S\) 的一个子串。 \(S\)

洛谷P3804 后缀自动机入门题

你说的曾经没有我的故事 提交于 2019-11-29 12:47:01
先贴板子,留坑 #include<bits/stdc++.h> #define ll long long #define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0) #define rep(ii,a,b) for(int ii=a;ii<=b;++ii) #define per(ii,a,b) for(int ii=b;ii>=a;--ii) using namespace std;//head const int maxn=2e6+10,maxm=27+10; int casn,n,m,k; namespace sam{ int fa[maxn<<1],son[maxn<<1][maxm],len[maxn<<1]; int last,cnt,t[maxn],val[maxn],num[maxn],a[maxn]; void insert(int ch){ int pos=last,npos=++cnt; last=npos,len[npos]=len[pos]+1; for(;pos&&!son[pos][ch];pos=fa[pos]) son[pos][ch]=npos; if(!pos) fa[npos]=1; else { int q=son[pos][ch]; if(len[pos]+1==len[q])

[TJOI2015]弦论(后缀自动机)

帅比萌擦擦* 提交于 2019-11-29 01:55:51
传送门 题意: 对给定字符串 \(s\) ,求其第 \(k\) 小子串,重复串被计入以及不被计入这两种情况都需考虑。 思路: 首先构建后缀自动机,之后就考虑在后缀自动机上 \(dp\) 。 我们知道如果要考虑重复串,那么就会与一个结点的 \(endpos\) 集合的大小有关,对于一条边 \((u,v)\) ,如果结点 \(u\) 的 \(endpos\) 集合大小为 \(x\) ,那么就说明从 \(u\) 出发到达 \(v\) ,会多出 \(x\) 种选择。 如果不考虑重复串,直接强制集合大小为 \(1\) 即可。 之后逆着拓扑序 \(dp\) 就行,求出从每个结点出发的子串个数。 最后求第 \(k\) 小的时候,有一些细节需要注意一下,比如从 \(u\) 到 \(v\) , \(k\) 应该减去 \(|endpos(v)|\) ,因为现在的字符串会有 \(|endpos(v)|\) 个。 感觉后缀自动机好神奇...好多的性质以及用法... #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e6 + 5; struct node{ int ch[26]; int len, fa; node(){memset(ch, 0, sizeof(ch)), len = 0;}

后缀自动机专题(hihocoder)

空扰寡人 提交于 2019-11-29 00:43:52
传送门 #1445 : 后缀自动机二·重复旋律5 题意: 给出字符串 \(s\) ,询问字符串 \(s\) 中有多少不同的子串。 思路: 考虑对 \(s\) 建后缀自动机,那么 \(\sum (len[i]-len[fa[i]])\) 即为答案。 还可以考虑 \(dp\) ,设 \(dp[i]\) 为从 \(i\) 出发不同子串的个数,那么 \(dp[i]=\sum_{(i,j)\in Edge}dp[j]+1\) 。 \(dp[1]\) 即为答案。 #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1000006; char s[N]; struct node{ int ch[26]; int len, fa; node(){memset(ch, 0, sizeof(ch)), len = 0;} }dian[N << 1]; int last, tot; void add(int c) { int p = last; int np = last = ++tot; dian[np].len = dian[p].len + 1; for(; p && !dian[p].ch[c]; p = dian[p].fa) dian[p].ch[c] = np; if(!p)