Skip to content

后缀自动机 ​

性质 ​

  1. 一个起点若干个终点。原串所有子串与从起点开始的路径一一对应,不重不漏

  2. 每个点包含若干子串。且这些子串一定是里面最长子串的连续后缀

  3. SAM要考虑的边

    • 普通边。相当于在某个状态表示下添加一个字符
    • Link,father。表示将该状态表示的最短子串删除首字符。这类边构成一棵树

构造 ​

  1. endpos(s):子串所有出现的位置的(尾字母下标)的集合。SAM每个状态都一一对应一个endpos等价类
  2. endpos的性质:
  • 若s1是s2的后缀,则endpos(s1)包含endpos(s2)
  • 两个不同的子串,要么包含关系,要么交集为空
  • 两个子串endpos相同,短串为长串的后缀
  • 如果两个子串endpos相同,那么他们之间的串也属于当前集合

问题 ​

  1. 如何求不同子串的数量?
    对于每个点,求max[i]-min[i]+1的总和,max[i]在构造的时候维护,min[i]=max[j]+1j为Link连向的边
  2. 每个子串的出现次数?
    endpos的集合大小

模板 ​

cpp
LL f[N];//f为endpos的集合大小
vector<int> adj[N];
struct SAM{
    struct Node{
        int len,link;
        array<int,26> ne;
        Node():len{},link{},ne{}{}
    };
    //最多2n-1个节点,3n-4条边
    vector<Node> t;
    int lst;
    SAM(){
        init();
    }
    void init(){
        t.assign(2,Node());
        lst=1;
    }
    int newNode(){
        t.emplace_back();
        return t.size()-1;
    }
    int extend(int p,int c){
        if(t[p].ne[c]){
            int q=t[p].ne[c];
            if(t[q].len==t[p].len+1)return q;
            int r=newNode();
            t[r]=t[q];
            t[r].len=t[p].len+1;
            t[q].link=r;
            while(p&&t[p].ne[c]==q){
                t[p].ne[c]=r;
                p=t[p].link;
            }
            return r;
        }
        int cur=newNode();
        lst=cur;
        t[cur].len=t[p].len+1;
        while(p&&!t[p].ne[c]){
            t[p].ne[c]=cur;
            p=t[p].link;
        }
        if(!p)t[cur].link=1;
        else t[cur].link=extend(p,c);
        return cur;
    }
    int extend(int p,char c,char offset='a'){
        return extend(p,(int)c-offset);
    }
    int next(int p,int c){
        return t[p].ne[c];
    }
    int next(int p,char c,char offset='a'){
        return next(p,(int)c-offset);
    }
    int link(int p){
        return t[p].link;
    }
    int len(int p){
        return t[p].len;
    }
    int size(){
        return t.size();
    }
};
LL ans=0;
SAM sam;
void dfs(int u){
    for(auto v:adj[u]){
        dfs(v);
        f[u]+=f[v];
    }
    if(f[u]>1)ans=max(ans,sam.len(u)*f[u]);
}
void solve(){
    string s;
    cin>>s;
    for(auto c:s){
        int tmp=sam.extend(sam.lst,c);
        f[tmp]=1;
    }
    for(int i=2;i<sam.size();i++)adj[sam.link(i)].push_back(i);
    dfs(1);
    cout<<ans<<"\n";
}

最长公共子串 ​

  • 对第一个串建后缀自动机
  • 对于每个串,寻找以i开头对应的最长串的自动机节点,标记
  • 每次处理完一个串后,还要更新它的link节点的串
  • 处理完所有串后,对所有节点的最长长度取max
cpp
struct SAM{
    struct Node{
        int len,link;
        array<int,26> ne;
        Node():len{},link{},ne{}{}
    } t[N];
    //最多2n-1个节点,3n-4条边
    int lst=1,tot=1;
    SAM(){
        init();
    }
    void init(){}
    int newNode(){
        ++tot;
        return tot;
    }
    int extend(int p,int c){
        if(t[p].ne[c]){
            int q=t[p].ne[c];
            if(t[q].len==t[p].len+1)return q;
            int r=newNode();
            t[r]=t[q];
            t[r].len=t[p].len+1;
            t[q].link=r;
            while(p&&t[p].ne[c]==q){
                t[p].ne[c]=r;
                p=t[p].link;
            }
            return r;
        }
        int cur=newNode();
        lst=cur;
        t[cur].len=t[p].len+1;
        while(p&&!t[p].ne[c]){
            t[p].ne[c]=cur;
            p=t[p].link;
        }
        if(!p)t[cur].link=1;
        else t[cur].link=extend(p,c);
        return cur;
    }
    int extend(int p,char c,char offset='a'){
        return extend(p,(int)c-offset);
    }
    int next(int p,int c){
        return t[p].ne[c];
    }
    int next(int p,char c,char offset='a'){
        return next(p,(int)c-offset);
    }
    int link(int p){
        return t[p].link;
    }
    int len(int p){
        return t[p].len;
    }
    int size(){
        return tot+1;
    }
};
vector<int> adj[N];
int now[N],ans[N],res;
SAM sam;
void dfs(int u){
    for(auto v:adj[u]){
        dfs(v);
        now[u]=max(now[u],now[v]);
    }
}
void solve(){
    string s;
    cin>>s;
    for(auto c:s)sam.extend(sam.lst,c);
    for(int i=1;i<sam.size();i++)adj[sam.link(i)].push_back(i),ans[i]=sam.len(i);
    while(cin>>s){
        memset(now,0,sizeof now);
        int p=1,t=0;
        for(int i=0;i<s.size();i++){
            int c=s[i]-'a';
            while(p>1&&!sam.next(p,c))p=sam.link(p),t=sam.len(p);
            if(sam.next(p,c))p=sam.next(p,c),t++;
            now[p]=max(now[p],t);
        }
        dfs(1);
        for(int i=1;i<sam.size();i++)ans[i]=min(ans[i],now[i]);
    }
    for(int i=1;i<sam.size();i++)res=max(res,ans[i]);
    cout<<res<<"\n";
}