后缀trie树 & 后缀自动机

后缀trie树

后缀trie树 就是在一个字典树里有且只有一个单词所有后缀
列出字符串 M I S S I S S I P P I MISSISSIPPI 的所有后缀

MISSISSIPPI 
 ISSISSIPPI 
  SSISSIPPI 
   SISSIPPI 
    ISSIPPI 
     SSIPPI 
      SIPPI 
       IPPI 
        PPI 
         PI 
          I

后缀trie树就是把所有后缀全部存进字典树里面
在这里插入图片描述
接下来是构造后缀trie树
第一种很简单面,就是按照定义将所有后缀依次插入到字典树里面

第二种就是一个很神奇的操作
比如说 a b a a b abaab

abaab
 baab
  aab
   ab
    b

先把所有后缀右对齐,然后从上到下开始操作
先开始把第一个字符插进去
在这里插入图片描述
红色箭头的指向的节点和子叶节点都是下一个字符要插入的点
在这里插入图片描述
然后在该节点下插入该字符,并且让红色箭头指向刚才插入的那个节点
在这里插入图片描述
要是红色箭头下面有要插入的字符,那就不必新建节点,直接指向该节点
在这里插入图片描述

后缀自动机 (SAM)

后缀自动机是一张有向无环图,其中顶点是状态,而边代表了状态之间的转移。
初始状态被记作 t 0 t_0 ,终止状态用星号 ( ) (*) 标记
失配指针fail指向的节点当前节点 (top) 所表示的字符串的最大后缀
在这里插入图片描述
从起点开始构建,第一个字符直接插入在根节点后面,失配指针指向根节点
在这里插入图片描述
因为第二个节有最大后缀 a 所以不需要在建一个新的节点
在这里插入图片描述
要是没有最大后缀,那就只能从根节点出发,连接该节点
在这里插入图片描述
你会发现从根节点出发到标 ( ) (*) 节点的所有路径表示的就是这个字符串的所有后缀
在这里插入图片描述
现在就是用到fail的时候了
要是第二次因为同一条边而fail到同一个节点,那么这个节点就得往下新建一个节点
新建的这个节点就有了第一次访问这个节点的性质,
并且让当前节点指向和第一次节点的fail指向新建的节点
完善这个新建的节点
在这里插入图片描述
fail指针主要还是用来完善新建节点的,并且加速图的建立

构造后缀自动机思路:
从当前字符(end)的前一个(last)开始跑fail,要是fail指向的节点不存在这个字符,那么将两个节点相连
跑到结束的时候会有两种情况
第一种是跑的了根节点(0)所指的fail,那么直接将根节点和最后节点(end)相连
第二种就是fail指向的节点下有这个字符,那么需要判断这个节点所表示的字符串是不是与(end)所表示的字符串相同
要是不相同,就要新建一个节点(np),新建的这个节点将保存先前访问过这个节点的信息
然后继续跑fail 要是fail指向的节点存在这个这个字符,那么连接节点(np)
目的就是找最长后缀相等的前缀

code
/*
Glass Beads
POJ - 1509
https://cn.vjudge.net/problem/POJ-1509#author=hzoi2017_gyz
题意:给一个字符串S,每次可以将它的第一个字符移到最后面,求这样能得到的字典序最小的字符串。 如BBAAB,最小的就是AABBB
解法:后缀自动机
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#define maxn 100000
using namespace std;
struct node{
  int len,fail;
  int next[26];
  void init(){
    fail=-1;
    len=0;
    memset(next,-1,sizeof next);
  }
}a[maxn];
int top,last;
char s[maxn];
void init(){
  top=last=0;
  a[top++].init();
}
int newnode(){
  a[top].init();
  return top++;
}
void add(int c){
  int end=newnode();
  int now=last;
  a[end].len=a[last].len+1;
  for(;now!=-1&&a[now].next[c]==-1; now=a[now].fail){
    a[now].next[c]=end;
  }
  if(now==-1) a[end].fail=0;
  else{
    int next=a[now].next[c];
    if(a[now].len+1==a[next].len) a[end].fail=next;
    else{
      int np = newnode();
      a[np]=a[next];
      a[np].len=a[now].len+1;
      a[end].fail=a[next].fail = np;
      for(;now!=-1&&a[now].next[c]==next;now=a[now].fail){
        a[now].next[c] = np;
      }
    }
  }
  last = end;
}
int main(){
  int T;
  cin>>T;
  while(T--){
    init();
    cin>>s;
    int len=strlen(s);
    for(int i=0;i<len*2;i++){
      add(s[i%len]-'a');
    }
    int ans=0;
    for(int i=0;i<len;i++){
      for(int j=0;j<26;j++){
        if(a[ans].next[j]!=-1){
          ans=a[ans].next[j];
          break;
        }
      }
    }
    cout<<a[ans].len-len+1<<endl;
  }
}

更多例题在这里

发布了70 篇原创文章 · 获赞 22 · 访问量 6500

猜你喜欢

转载自blog.csdn.net/weixin_44410512/article/details/100799519
今日推荐