其他分享
首页 > 其他分享> > 广义SAM

广义SAM

作者:互联网

参考
还有首先你要会SAM吧~

用途

相比与单串SAM,广义自动机能存储的是多个字符串

有两种写法,第一种是离线利用trie树结构,第二种是在线伪广义SAM

离线+Trie

首先构建出trie树。
然后在trie树上BFS(),用\(pos[u]\)映射trie树上\(u\)节点对应SAM上的节点。

为什么不dfs,因为时间复杂度是trie树上所有叶子到根的距离和,证明BFS\(O(n)\)复杂度具体见上面参考博客

code:

struct SAM {
  int tr[N<<1][M],nd,len[N<<1],par[N<<1],pos[N*M];
  queue<int> Q;
  int Insert(int x,int lst) {
        ...略,返回新节点编号
  }
  void BFS() {
      Q.push(1);pos[1]=1;
      while(!Q.empty()) {
          int u=Q.front(); Q.pop();
          for(int i=0;i<26;i++) {
              int v=T.go[u][i];
              if(!v)continue;
              pos[v]=Insert(i,pos[u]);
              Q.push(v);
          }
       }
}S;

相信上面code的你能够很好的理解

在线

虽然它很伪,但通常跑的比上面那种快,而且对于很多问题在上面处理起来很方便。
方便讲解,先放一份原来插单串的code:

int Insert(int x,int lst) {
    int p=lst,np=++nd;len[np]=len[p]+1;
    for(;!tr[p][x];p=par[p])tr[p][x]=np;
    if(!p) {par[np]=1;}
    else {
        int q=tr[p][x];
        if(len[q]==len[p]+1) {par[np]=q;}
        else {
            int nq=++nd;par[nq]=par[q];len[nq]=len[p]+1;
            for(int j=0;j<26;j++)tr[nq][j]=tr[q][j];
            par[q]=par[np]=nq;
            for(;tr[p][x]==q;p=par[p]) tr[p][x]=nq;
        }
    }
    return np;
}

多串总体的区别就是每加入一个新串前让lst=1(root)
这样会出现之前插单串没出现过的情况:之前p=lst往上找tr[p][x]!=0之前肯定会存在tr[p][x]=0
然而现在可能一开始tr[lst][x]!=0,这有什么问题吗?就跟原来一样分两类讨论(拆点或不拆点)
问题就在于,这时新加的np是个空点,因为它是完全没有必要的。
令q=tr[lst][x]

struct SAM {
    int tr[N<<1][M],nd,len[N<<1],par[N<<1];
    queue<int> Q;
    SAM() {nd=1;}
    int Insert(int x,int lst) {
        int p=lst;
        if(tr[p][x]) {
            int q=tr[p][x];
            if(len[q]==len[p]+1)return q;
            int nq=++nd;par[nq]=par[q];len[nq]=len[p]+1;
            for(int j=0;j<c;j++)tr[nq][j]=tr[q][j];
            for(;tr[p][x]==q;p=par[p]) tr[p][x]=nq;
            return par[q]=nq;
        }
        int np=++nd;len[np]=len[p]+1;
        for(;!tr[p][x];p=par[p])tr[p][x]=np;
        if(!p) {par[np]=1;}
        else {
            int q=tr[p][x];
            if(len[q]==len[p]+1) {par[np]=q;}
            else {
                int nq=++nd;par[nq]=par[q];len[nq]=len[p]+1;
                for(int j=0;j<c;j++)tr[nq][j]=tr[q][j];
                par[q]=par[np]=nq;
                for(;tr[p][x]==q;p=par[p]) tr[p][x]=nq;
            }
        }
        return np;
    }
}S;

ps.最后强调一点:每个结点中的子串可能来自多个字符串。然而所有来源于相同字符串的子串endpos集合保证相同,而来源于不同字符串的endpos不一定相同.
很好理解(后面加一堆废话),假如加入了i-1个字符串(此时SAM满足上面性质)。第i个字符串的加入,会拆分原来的点(拆点不影响上面性质,只不过是对于同一个字符串同一个等价类被拆成多个节点罢了)。新加的i字符串肯定是满足的。

标签:SAM,int,tr,len,lst,广义,np,nq
来源: https://www.cnblogs.com/bestime/p/15981865.html