后缀自动机 SAM 学习笔记

参考资料

hihoCoder1441
hihoCoder1445
史上最通俗的后缀自动机详解

练习题

hihocoder1449
hihocoder1457
hihocoder1465
hihocoder1413

笔记

字符串 a a b b a b d aabbabd aabbabd
在这里插入图片描述
在这里插入图片描述

模板
struct SuffixAutomaton{
    int last=1,cnt=1,len[N],ch[N][26],fa[N];
    //sum[i]状态i为起点的子串个数,size[i]状态i的longest()出现次数
    int c[N],a[N],size[N],sum[N];
    void Insert(int c){
        int p=last,np=++cnt; last=np;
        len[np]=len[p]+1; size[np]=1;
        for (;p && !ch[p][c];p=fa[p]) ch[p][c]=np;
        if (!p) {fa[np]=1;return;}
        int q=ch[p][c];
        if (len[p]+1==len[q]) {fa[np]=q;return;}
        int nq=++cnt;
        memcpy(ch[nq],ch[q],sizeof ch[q]);
        len[nq]=len[p]+1; fa[nq]=fa[q]; fa[q]=fa[np]=nq;
        for (;ch[p][c]==q;p=fa[p]) ch[p][c]=nq; 
    }
    //t==0表示相同子串不重复计算,t==1表示重复计算
    void Work(){
        for (int i=1;i<=cnt;i++) c[len[i]]++;
        for (int i=1;i<=cnt;i++) c[i]+=c[i-1];
        for (int i=1;i<=cnt;i++) a[c[len[i]]--]=i;
        for (int i=cnt;i;i--) if (t) size[fa[a[i]]]+=size[a[i]];
            else size[a[i]]=1;
        size[1]=0;
        for (int i=cnt;i;i--){
            sum[a[i]]=size[a[i]];
            for (int j=0;j<26;j++)
                if (ch[a[i]][j]) sum[a[i]]+=sum[ch[a[i]][j]];
        }
    }
};

DAG上DP
确定遍历顺序,数组 A[] 为结点遍历顺序

for(int i=1;i<=node;i++) t[len[i]]++;
for(int i=1;i<=node;i++) t[i]+=t[i-1];
for(int i=1;i<=node;i++) A[t[len[i]]--]=i;
一个结点出发的不同子串个数

对于一个节点 i i i f [ i ] f[i] f[i] 表示从 i i i 出发的子串个数(不含空串)。那么, f [ i ] f[i] f[i] 就等于 ∑ ( i , j ) ∈ E d g e ( f [ j ] + 1 ) \sum_{(i,j)\in Edge}(f[j]+1) (i,j)Edge(f[j]+1) f [ 1 ] f[1] f[1]即是答案。
转移顺序的话,就按照 A [ ] A[] A[] 的倒序

for(int i=cnt;i>=1;i--) 
    for(int j=0;j<26;j++) if (ch[A[i]][j]) f[A[i]]+=f[ch[A[i]][j]]+1;
一个子串出现的次数等于其所在状态的 e n d p o s endpos endpos 的大小
size[np]=1;
for (int i=cnt;i;i--) size[fa[a[i]]]+=size[a[i]];
一个状态所包含的子串的数量
for(int i=1;i<=cnt;i++)
    for(int j=0;j<26;j++) if (ch[a[i]][j]) num[ch[a[i]][j]]+=num[a[i]];
最长公共前缀
int u=1,l=0;
for(int i=1;i<=m;i++){
    int c=T[i]-'a';
    while(u&&!ch[u][c]) u=fa[u],l=len[u];
    if (ch[u][c]) u=ch[u][c],l++; else u=1,l=0;
}

在这里插入图片描述

广义后缀自动机
struct SAM {
    int ch[N][26],fa[N],len[N],last[N],a[N],t[N],size[N],cnt=1,root=1;
    int add(int p, int c) {
        if(ch[p][c]&&len[ch[p][c]]==len[p]+1) return ch[p][c];
        int np = ++cnt,fg=0;
        size[np] = 1; len[np] = len[p] + 1;
        while (!ch[p][c] && p) ch[p][c]=np,p=fa[p];
        if (!p) {
            fa[np] = root;
        }else{
            int q = ch[p][c];
            if (len[p]+1==len[q]) {
                fa[np]=q;
            }else{
                if (len[p]+1==len[np]) fg=1;
                int nq = ++cnt;
                memcpy(ch[nq], ch[q], sizeof(ch[q]));
                len[nq]=len[p]+1;
                fa[nq]=fa[q];
                fa[q]=fa[np]=nq;
                while (p&&ch[p][c]==q) ch[p][c]=nq,p=fa[p];
                return fg?nq:np;
            }
        }
        return np;
    }
    void init() {
        //如多次建立自动机,加入memset操作
        root = cnt = 1;
    }
    void build() {
        init();
        queue<int> q;
        q.push(0), last[0] = root;
        while (!q.empty()) {
            int p=q.front(); q.pop();
            for (int i=0;i<trie[p].size();i++) {
                last[trie[p][i]] = add(last[p], s[trie[p][i]] - 'A');
                q.push(trie[p][i]);
            }
        }
        for (int i=1;i<=cnt;i++) {t[len[i]]++; }
        for (int i=1;i<=cnt;i++) {t[i]+=t[i - 1]; }
        for (int i=1;i<=cnt;i++) {a[t[len[i]]--] = i; }
        for (int i=cnt;i>root;i--) size[fa[a[i]]] += size[a[i]];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值