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

来源:互联网 发布:数据维护工具v3.8 编辑:程序博客网 时间:2024/06/05 23:53

后缀自动机(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包含的最短的子串。

有几点性质:

  1. 对于S的两个子串s1和s2,不妨设length(s1) <= length(s2),那么 s1是s2的后缀当且仅当endpos(s1) ⊇ endpos(s2),s1不是s2的后缀当且仅当endpos(s1) ∩ endpos(s2) = ∅。
  2. 对于一个状态st,以及任意s∈substrings(st),都有s是longest(st)的后缀。
  3. 对于一个状态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%点,注意试试这组数据

dbddba3dbdbab

1 0