Suffix Automaton Tutorial

3 minute read

Published:

This is a comprehensive tutorial on Suffix Automaton (SAM) with detailed problem solutions.

Problem 1: HDU 4622 - Reincarnation

题意: 询问每个子串里包含的不同的子串的个数。

思路: 枚举每一个点作为起点做后缀自动机,维护自动机每个节点能有多少条路径到达。

#include <stdio.h>
#include <string.h>
#include <vector>
#include <iostream>
#include <set>
#include <map>
#include <queue>
#define ll long long
#define clr(a,b) memset(a,b,sizeof(a))
#define pii pair<int,int>
#define mpr(a,b) make_pair(a,b)
using namespace std;

const int ch = 26;
const int N=100000;
class SAM{
    public:
    int f[N],chd[N][ch+1],len[N],sw[200];
    //ch+1 number of tempt suffix at status
    int last,sz;
    void init(){
        clr(chd[0],0);
        f[0] = -1;
        chd[0][ch] = 1;
        last = 0;
        sz = 0;
        for(int i=0;i<26;i++)
            sw[i+'a'] = i;
    }
    int add(char inc,int l){
        int ans = 0;
        int c = sw[inc];
        int x = last;
        last = ++sz;
        clr(chd[sz],0);
        len[sz] = l;
        for(;x!=-1&&chd[x][c]==0;x=f[x]){
            chd[x][c] = sz;
            chd[last][ch] += chd[x][ch];
            ans += chd[x][ch];
        }
        if(x == -1){
            f[sz]=0;
        }
        else{
            int y = chd[x][c];
            if(len[y] == len[x]+1){
                f[sz]=y;
            }
            else{
                sz++;
                len[sz] = len[x] + 1;
                
                memcpy(chd[sz],chd[y],ch*4);
//                for(int i=0;i<ch;i++) chd[sz][i] = chd[y][i];
                chd[sz][ch] = 0;

                f[sz] = f[y];
                f[last] = f[y] = sz;

                for(;x!=-1&&chd[x][c]==y;x=f[x]){
                    chd[x][c] = sz;
                    chd[y][ch] -= chd[x][ch];
                    chd[sz][ch] += chd[x][ch];
                }
            }
        }
        return ans;
    }
}sam;

char s[100000];
int n,m;


int g[2005][2005];
int main(){
//    freopen("/home/zyc/Documents/Code/cpp/in","r",stdin);
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%s",s);
        n = strlen(s);
        for(int i=0;i<n;i++){
            sam.init();
            for(int j=i;j<n;j++){
                if(j!=i)
                    g[i][j] = g[i][j-1] + sam.add(s[j],j-i+1);
                else
                    g[i][j] = sam.add(s[j],j-i+1);
            }
        }
        int Q;
        scanf("%d",&Q);
        while(Q--){
            int l,r;
            scanf("%d%d",&l,&r);
            l--;r--;
            printf("%d\n",g[l][r]);
        }
    }
    return 0;
}

Problem 2: HDU 4641 - Dynamic Substring Count

题意: 给一个串在末尾动态增加字符,询问当前串中至少出现k次的子串个数。

思路: 与上题相比还需要维护增加字符时,以新加入字符为串尾的串的相应节点出现次数,只要更新parent树中当前last的祖先即可。

#include <stdio.h>
#include <string.h>
#include <vector>
#include <iostream>
#include <set>
#include <map>
#include <queue>
#define ll long long
#define clr(a,b) memset(a,b,sizeof(a))
#define pii pair<int,int>
#define mpr(a,b) make_pair(a,b)
using namespace std;

const int ch = 26;
const int N=1000000;
int k;

class SAM{
    public:
    int f[N],chd[N][ch+2],len[N],sw[200];
    //ch+1 number of tempt suffix at status
    int last,sz;
    void init(){
        clr(chd[0],0);
        f[0] = -1;
        chd[0][ch] = 1;
        last = 0;
        sz = 0;
        for(int i=0;i<26;i++)
            sw[i+'a'] = i;
    }
    ll add(char inc,int l){
        int c = sw[inc];
        int x = last;
        last = ++sz;
        clr(chd[sz],0);
        len[sz] = l;
        for(;x!=-1&&chd[x][c]==0;x=f[x]){
            chd[x][c] = sz;
            chd[last][ch] += chd[x][ch];
        }
        if(x == -1){
            f[sz]=0;
        }
        else{
            int y = chd[x][c];
            if(len[y] == len[x]+1){
                f[sz]=y;
            }
            else{
                sz++;
                len[sz] = len[x] + 1;
                for(int i=0;i<ch;i++) chd[sz][i] = chd[y][i];
                chd[sz][ch] = 0;
                chd[sz][ch+1] = chd[y][ch+1];

                f[sz] = f[y];
                f[last] = f[y] = sz;

                for(;x!=-1&&chd[x][c]==y;x=f[x]){
                    chd[x][c] = sz;
                    chd[y][ch] -= chd[x][ch];
                    chd[sz][ch] += chd[x][ch];
                }

            }
        }
        ll ans = 0;
        x = last;
        for(x = last;x!=0;x=f[x]){
            if(chd[x][ch+1]>=k) break;
            chd[x][ch+1] ++;
            if(chd[x][ch+1]==k)
                ans += chd[x][ch];
        }
        return ans;
    }
}sam;

char s[100000];
int n,m;


int main(){
//    freopen("/home/zyc/Documents/Code/cpp/in","r",stdin);
    while(scanf("%d%d%d",&n,&m,&k)!=EOF){
        scanf("%s",s);
        sam.init();
        ll ans = 0;
        for(int i=0;i<n;i++)
            ans += sam.add(s[i],i+1);
        while(m--){
            int type;
            scanf("%d",&type);
            if(type==1){
                char c[2];
                scanf("%s",c);
                ans += sam.add(c[0],++n);
            }
            else{
                printf("%lld\n",ans);
            }
        }
    }
    return 0;
}

Problem 3: HDU 2609 - 循环不同构

题意: 给你n个串,问有多少个是循环不同构。

思路: 可以使用最小表示法来求,后缀自动机的话先把串扩展成两倍,加进自动机,然后在自动机里求长度为原来长度的最小的串即可,需要遍历一遍。最后用trie树判重。

Problem 4: SPOJ NSUBSTR - 最大出现次数

题意: 求一个串中长度为i的串最多出现的次数。

思路: 记录自动机每个节点到达的次数,利用parent树来做。然后利用自动机的性质得到每个节点最长到达的串,即构造时的len,然后从大到小更新一遍。

Problem 5: SPOJ SUBLEX - 第k小子串

题意: 询问一个串中第k小的不同的子串。

思路: 关键要得到自动机每个节点后面能够有多少种不同走法,有向无环图做下dp。然后走一遍自动机。

Problem 6: SPOJ LCS / LCS2 - 最长公共子串

题意: 求n个串的最长公共子串。

思路: 这两题比较类似。先以第一个串构造自动机,然后每个串都在自动机上走一遍,维护每个节点所能匹配的最长的串,对于所有串每个节点取个匹配的最小值,再对所有节点取个最大值即可。

Problem 7: BZOJ 2806

这题比较综合。就说一下后缀自动机的部分:要得到一个串在某个位置往前与其他模版串所能匹配的最长长度。把模版串用’$’号串起来,建立后缀自动机。然后一开始的串走一遍就可以了。

Problem 8: HDU 4436 - 子串数字和

题意: 给你多个由0~9组成的字符串,问其中所有子串代表的不同的数字的和。

思路: 位数高的在前面,所以先把串反过来,让位数高的后加进自动机。多个串要判重所以先用’$’连接起来,建立自动机。要得到每个节点后面的其他节点的值以及出现次数,然后当前节点的值就是 dp[u] = dp[v] * 10 + i * p[v] (i为转移的数字,p[v]为出现次数),还是dag上的dp。注意要把i=0的情况处理好。答案就是dp[0]。

Problem 9: SPOJ COT4

看了一眼别人的做法,只能说我没打过ACM。有空在写。。。。


This tutorial was originally written in 2013 as competitive programming notes.