您的位置:首页 > 其它

后缀自动机学习笔记1(hiho127周)

2016-12-14 16:07 701 查看
后缀自动机(Suffix Automaton,简称SAM)。

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}

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,它对应的后缀自动机是一个最小的确定有限状态自动机(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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息