后缀自动机学习笔记1(hiho127周)
2016-12-14 16:07
701 查看
后缀自动机(Suffix Automaton,简称SAM)。
= {1, 2, 5}, endpos("abba") = {5}。
将所有子串的endpos都求出来。如果两个子串的endpos相等,就把这两个子串归为一类。最终这些endpos的等价类就构成的SAM的状态集合。例如对于S="aabbabd":
substrings(st)表示状态st中包含的所有子串的集合,
longest(st)表示st包含的最长的子串,
shortest(st)表示st包含的最短的子串。
有几点性质:
对于S的两个子串s1和s2,不妨设length(s1) <= length(s2),那么 s1是s2的后缀当且仅当endpos(s1) ⊇ endpos(s2),s1不是s2的后缀当且仅当endpos(s1) ∩ endpos(s2) = ∅。
对于一个状态st,以及任意s∈substrings(st),都有s是longest(st)的后缀。
对于一个状态st,以及任意的longest(st)的后缀s,如果s的长度满足:length(shortest(st)) <= length(s) <= length(longsest(st)),那么s∈substrings(st)
对于字符串S="aabbabd",它的后缀自动机是:
S是开始状态,9是结束状态。通过这个图可以看出状态S到状态9的蓝线所有经过的路线的字符都是aabbabd的一个后缀,例如:S-1-8-9经过的路线是abd正好是aabbabd的一个后缀数组。再比如例如字符串aabb的所有后缀数组即为从状态S到状态4经过的所有路线的字符组成的字符串,例如S-1-8-4经过的路线为abb。
这里有一个问题就是b是aabb的一个后缀数组,但是从状态S到状态4经过的路线中没有b的字符串
,这是因为b在状态3(字符串aab)中也是它的后缀数组。所以引入了一个中间状态5,这样你会发现对于整个字符串aabbabd来说它的自没有串并没有减少,而且不会出现重复(这里从状态S出发到任意一个状态的蓝线经过的字符串都是aabbabd的一个字串)。
对于一个状态st和一个字符c∈next(st),可以定义转移函数trans(st, c) = x | longest(st) + c ∈ substrings(x) ,具体怎么转移在下一个笔记里。
性质:对于一个状态st来说和一个next(st)中的字符c,你会发现substrings(st)中的所有子串后面接上一个字符c之后,新的子串仍然都属于同一个状态。比如对于状态4,next(4)={a},aabb,abb,bb后面接上字符a得到aabba,abba,bba,这些子串都属于状态6
问题:
输入
第一行包含一个字符串S,S长度不超过50。
第二行包含一个整数N,表示询问的数目。(1 <= N <= 10)
以下N行每行包括一个S的子串s,s不为空串。
输出
对于每一个询问s,求出包含s的状态st,输出一行依次包含shortest(st)、longest(st)和endpos(st)。其中endpos(st)由小到大输出,之间用一个空格分割。
样例输入
aabbabd
5
b
abbab
aa
aabbab
bb
样例输出
b b 3 4 6
bab aabbab 6
aa aa 2
bab aabbab 6
bb aabb 4
代码:
一开始一直是过90%点,注意试试这组数据
SAM的States
字串结束集合(endpos):对于S的一个子串s,endpos(s) = s在S中所有出现的结束位置集合。还是以S="aabbabd"为例,endpos("ab") = {3, 6}(这里说的是位置,不要和下面那个图所说的状态1、2、3混淆,这里说的“ab”位置是“aabbabd”,这两个位置为3,6),因为"ab"一共出现了2次,结束位置分别是3和6。同理endpos("a")= {1, 2, 5}, endpos("abba") = {5}。
将所有子串的endpos都求出来。如果两个子串的endpos相等,就把这两个子串归为一类。最终这些endpos的等价类就构成的SAM的状态集合。例如对于S="aabbabd":
状态 | 字符串 | endpos |
S | 空串 | {0,1,2,3,4,5,6} |
1 | a | {1,2,5} |
2 | aa | {2} |
3 | aab | {3} |
4 | aabb,abb,bb | {4} |
5 | b | {3,4,6} |
6 | aabba,abba,bba,ba | {5} |
7 | aabbab,abbab,bbab,bab | {6} |
8 | ab | {3,6} |
9 | aabbabd,abbabd,bbabd,babd,abd,bd,d | {7} |
longest(st)表示st包含的最长的子串,
shortest(st)表示st包含的最短的子串。
有几点性质:
对于S的两个子串s1和s2,不妨设length(s1) <= length(s2),那么 s1是s2的后缀当且仅当endpos(s1) ⊇ endpos(s2),s1不是s2的后缀当且仅当endpos(s1) ∩ endpos(s2) = ∅。
对于一个状态st,以及任意s∈substrings(st),都有s是longest(st)的后缀。
对于一个状态st,以及任意的longest(st)的后缀s,如果s的长度满足:length(shortest(st)) <= length(s) <= length(longsest(st)),那么s∈substrings(st)
后缀自动机
对于一个字符串S,它对应的后缀自动机是一个最小的确定有限状态自动机(DFA),接受且只接受S的后缀。对于字符串S="aabbabd",它的后缀自动机是:
S是开始状态,9是结束状态。通过这个图可以看出状态S到状态9的蓝线所有经过的路线的字符都是aabbabd的一个后缀,例如:S-1-8-9经过的路线是abd正好是aabbabd的一个后缀数组。再比如例如字符串aabb的所有后缀数组即为从状态S到状态4经过的所有路线的字符组成的字符串,例如S-1-8-4经过的路线为abb。
这里有一个问题就是b是aabb的一个后缀数组,但是从状态S到状态4经过的路线中没有b的字符串
,这是因为b在状态3(字符串aab)中也是它的后缀数组。所以引入了一个中间状态5,这样你会发现对于整个字符串aabbabd来说它的自没有串并没有减少,而且不会出现重复(这里从状态S出发到任意一个状态的蓝线经过的字符串都是aabbabd的一个字串)。
SAM的Suffix Links
Suffix Links实际就是上图的绿线,通过刚才的介绍发现,当前状态引入了中间状态,绿线就指向中间状态,例如上一段说的状态4的路线就指向状态5,否则指向状态S。我们可以发现一条状态序列:7->8->5->S。这个序列的意义是longest(7)即aabbab的后缀依次在状态7、8、5、S中。这个绿线在我们接下来的使用中有很大作用。SAM的Transition Function
next(st):st遇到的下一个字符集,有next(st) = {S[i+1] | i ∈ endpos(st)}。例如next(S)={S[1], S[2], S[3], S[4], S[5], S[6], S[7]}={a, b, d},next(8)={S[4], S[7]}={b, d}。这里可以看成就是从状态st节点发出的线的符号。对于一个状态st和一个字符c∈next(st),可以定义转移函数trans(st, c) = x | longest(st) + c ∈ substrings(x) ,具体怎么转移在下一个笔记里。
性质:对于一个状态st来说和一个next(st)中的字符c,你会发现substrings(st)中的所有子串后面接上一个字符c之后,新的子串仍然都属于同一个状态。比如对于状态4,next(4)={a},aabb,abb,bb后面接上字符a得到aabba,abba,bba,这些子串都属于状态6
实例
(这里用的爆搜,我的代码写的不好,但是也实现了,后面会有进步的):问题:
输入
第一行包含一个字符串S,S长度不超过50。
第二行包含一个整数N,表示询问的数目。(1 <= N <= 10)
以下N行每行包括一个S的子串s,s不为空串。
输出
对于每一个询问s,求出包含s的状态st,输出一行依次包含shortest(st)、longest(st)和endpos(st)。其中endpos(st)由小到大输出,之间用一个空格分割。
样例输入
aabbabd
5
b
abbab
aa
aabbab
bb
样例输出
b b 3 4 6
bab aabbab 6
aa aa 2
bab aabbab 6
bb aabb 4
代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace hiho127 { class Program { static bool equipment(List<int> l1, List<int> l2) { for (int i = 0; i < l1.Count; i++) { if (l1[i]!=l2[i]) { return false; } } return true; } static void Main(string[] args) { Dictionary<string, List<int>> myDic = new Dictionary<string, List<int>>(); string s = Console.ReadLine(); for (int i = 0; i < s.Length; i++) { for (int j = i; j >= 0; j--) { string temp = s.Substring(j, i - j + 1); if (myDic.ContainsKey(temp)) { myDic[temp].Add(i + 1); } else { List<int> lint = new List<int>(); lint.Add(i + 1); myDic.Add(temp, lint); } } } int n = int.Parse(Console.ReadLine()); for (int i = 0; i < n; i++) { string temp = Console.ReadLine(); List<int> ltem = myDic[temp]; for (int j = 0; j < myDic.Count; j++) { List<int> lint = myDic.ElementAt(j).Value; if (lint.Count == ltem.Count && equipment(lint,ltem)) { Console.Write(myDic.ElementAt(j).Key); break; } } for (int j = myDic.Count - 1; j >= 0; j--) { List<int> lint = myDic.ElementAt(j).Value; if (lint.Count == ltem.Count && equipment(lint, ltem)) { Console.Write(" " + myDic.ElementAt(j).Key); break; } } foreach (int item in ltem) { Console.Write(" " + item.ToString()); } Console.WriteLine(); } } } }注意:
一开始一直是过90%点,注意试试这组数据
dbddba 3 db dba b
相关文章推荐
- 后缀自动机学习笔记2(hiho128周)
- 后缀自动机 学习笔记
- SAM 后缀自动机——学习笔记
- [后缀自动机]【学习笔记】
- 【字符串数据结构后缀系列Part2】后缀自动机学习笔记
- 后缀自动机学习笔记
- 后缀自动机学习笔记
- [学习笔记] 后缀自动机学习笔记
- [Notes] 后缀自动机学习笔记
- 后缀自动机学习笔记
- 后缀自动机学习笔记
- 后缀自动机学习笔记
- 后缀自动机学习笔记
- 后缀自动机学习笔记
- 后缀自动机学习笔记3
- 【字符串数据结构后缀系列Part1】后缀数组学习笔记
- 后缀数组学习笔记
- 后缀自动机学习总结
- Androidx学习笔记(45)--- 获取文件的后缀名(java基本语法)
- 后缀自动机学习总结