後綴自動機專題

若是不算pre指針的話後綴自動機就是一個DAG,這是它能很方便地進行dp的前提。node

而pre指針返回什麼呢,返回的就是上一個的前綴包含改結點所表明子串的那個後綴,和AC自動機上的fail指針很像,都是爲了匹配。我目前學得不深,看不出和AC自動機的fail指針有什麼區別,用起來也幾乎同樣。c++

相比於字典樹和迴文樹,後綴自動機每一個結點會有多個父結點,能夠表示多種子串(從根節點到它的每條路徑都是一個子串),所以子串的信息只能在路徑中記錄,好比長度,而該子串說記錄的長度step,則是根結點到它的最遠距離,而某個子串的長度就是該表明該子串的路徑的長度了,並不必定是某個結點的step。數組

spoj1811ide

求兩個串的最長公共子串的長度。ui

對A串創建後綴自動機,對B串進行匹配,若是匹配失敗,沿着失敗指針往回走到第一個能匹配的位置繼續匹配(看起來似曾相識?沒錯,這不是AC自動機的過程嗎。。。),固然若是到根節點還不能繼續匹配,那就只有從頭再來了。這裏隨時記錄長度更新答案便可。spa

固然後綴數組也能夠作,把兩個串拼接起來,求lcp便可。。。而後後綴自動機好快。。。。在spoj上竟然60ms過了。。。指針

#include<bits/stdc++.h>
#define REP(i,a,b) for(int i=a;i<=b;i++)
#define MS0(a) memset(a,0,sizeof(a))

using namespace std;

typedef long long ll;
const int maxn=2000100;
const int INF=1e9+10;

struct SAM
{
    int ch[maxn][26];
    int pre[maxn],step[maxn];
    int last,tot;
    void init()
    {
        last=tot=0;
        memset(ch[0],-1,sizeof(ch[0]));
        pre[0]=-1;
        step[0]=0;
    }
    void add(int c)
    {
        c-='a';
        int p=last,np=++tot;
        step[np]=step[p]+1;
        memset(ch[np],-1,sizeof(ch[np]));
        while(~p&&ch[p][c]==-1) ch[p][c]=np,p=pre[p];
        if(p==-1) pre[np]=0;
        else{
            int q=ch[p][c];
            if(step[q]!=step[p]+1){
                int nq=++tot;
                step[nq]=step[p]+1;
                memcpy(ch[nq],ch[q],sizeof(ch[q]));
                pre[nq]=pre[q];
                pre[q]=pre[np]=nq;
                while(~p&&ch[p][c]==q) ch[p][c]=nq,p=pre[p];
            }
            else pre[np]=q;
        }
        last=np;
    }
    int find(char *s)
    {
        int len=strlen(s);
        int res=0,tmp=0;
        int u=0;
        REP(i,0,len-1){
            int c=s[i]-'a';
            if(~ch[u][c]) tmp++,u=ch[u][c];
            else{
                while(~u&&ch[u][c]==-1) u=pre[u];
                if(~u) tmp=step[u]+1,u=ch[u][c];
                else tmp=0,u=0;
            }
            res=max(res,tmp);
        }
        return res;
    }
};SAM sam;
char s[maxn],t[maxn];

void solve()
{
    sam.init();
    int len=strlen(s);
    REP(i,0,len-1) sam.add(s[i]);
    printf("%d\n",sam.find(t));
}

int main()
{
    freopen("in.txt","r",stdin);
    while(~scanf("%s%s",s,t)){
        solve();
    }
    return 0;
}
View Code

 spoj1812code

求多個串的最長公共子串的長度。blog

對第一個串創建SAM,每一個結點多維護兩個信息:當前串在該結點匹配到的最長長度Max,多個串在該結點匹配到的最短長度Min,而後逐個放到SAM去更新這些信息就能夠了,最後的結果顯然是全部結點Min的最大值。排序

而後須要注意的一點是(這一點也加深了我對後綴自動機的理解),這裏和AC自動機的fail指針不同的是這個比AC自動機具備拓撲關係,是純粹的DAG,更像迴文樹,所以匹配更新的時候就不要像AC自動機那樣匹配到一個就找沿着fail指針往回走了,由於這樣在SAM中是n^2的複雜度,可是能夠像迴文樹計算cnt同樣處理,匹配完以後逆序遍歷一遍,用每一個點更新它的pre結點。

固然後綴數組也能夠作,把串都拼接起來,而後二分長度在height數組上滑動窗口檢驗是否包含n個便可。。。而後後綴自動機真的好快。。。150ms。。。

#include<bits/stdc++.h>
#define REP(i,a,b) for(int i=a;i<=b;i++)
#define MS0(a) memset(a,0,sizeof(a))

using namespace std;

typedef long long ll;
const int maxn=1000100;
const int INF=1e9+10;

struct SAM
{
    int ch[maxn][26];
    int pre[maxn],step[maxn];
    int last,tot;
    int Min[maxn],Max[maxn];
    void init()
    {
        last=tot=0;
        memset(ch[0],-1,sizeof(ch[0]));
        pre[0]=-1;step[0]=0;
        Min[0]=Max[0]=0;
    }
    void add(int c)
    {
        c-='a';
        int p=last,np=++tot;
        step[np]=step[p]+1;
        memset(ch[np],-1,sizeof(ch[np]));
        Min[np]=Max[np]=step[np];
        while(~p&&ch[p][c]==-1) ch[p][c]=np,p=pre[p];
        if(p==-1) pre[np]=0;
        else{
            int q=ch[p][c];
            if(step[q]!=step[p]+1){
                int nq=++tot;
                step[nq]=step[p]+1;
                memcpy(ch[nq],ch[q],sizeof(ch[q]));
                Min[nq]=Max[nq]=step[nq];
                pre[nq]=pre[q];
                pre[q]=pre[np]=nq;
                while(~p&&ch[p][c]==q) ch[p][c]=nq,p=pre[p];
            }
            else pre[np]=q;
        }
        last=np;
    }
    void find(char *s)
    {
        int len=strlen(s);
        int u=0;
        int tmp=0;
        REP(i,0,tot) Max[i]=0;
        REP(i,0,len-1){
            int c=s[i]-'a';
            if(~ch[u][c]) tmp++,u=ch[u][c];
            else{
                while(~u&&ch[u][c]==-1) u=pre[u];
                if(~u) tmp=step[u]+1,u=ch[u][c];
                else tmp=0,u=0;
            }
            Max[u]=max(Max[u],tmp);
        }
        for(int i=tot;i>=1;i--) Max[pre[i]]=max(Max[pre[i]],Max[i]);
        REP(i,0,tot) Min[i]=min(Min[i],Max[i]);
    }
    int calc()
    {
        int res=0;
        REP(i,0,tot) res=max(res,Min[i]);
        return res;
    }
};SAM A;
char s[maxn];
int len;

int main()
{
    freopen("in.txt","r",stdin);
    scanf("%s",s);len=strlen(s);
    A.init();
    REP(i,0,len-1) A.add(s[i]);
    while(~scanf("%s",s)) A.find(s);
    printf("%d\n",A.calc());
    return 0;
}
View Code

 spoj8222

求一個串的長度爲1到len的出現最多的子串的出現的次數。

這道題弄了一下午,終於漸漸清晰了起來,這道題終於使我可以理解pre指針和AC自動機的區別了。pre指針所引出來的parent樹真是SAM的精髓。

對於這道題,雖然有不少要寫的,可是如今過於興奮不知從何寫起,,,有時間再補上。。。

須要注意的一點是,這裏雖然是parent樹是拓撲圖引出來的,但在parent樹遞推計算right的時候不能向迴文樹同樣直接逆序遍歷,會出錯,須要記一下度數作一次拓撲排序。

#include<bits/stdc++.h>
#define REP(i,a,b) for(int i=a;i<=b;i++)
#define MS0(a) memset(a,0,sizeof(a))

using namespace std;

typedef long long ll;
const int maxn=1000100;
const int INF=1e9+10;

char s[maxn];
int ans[maxn];
struct SAM
{
    int ch[maxn][26];
    int pre[maxn],step[maxn];
    int last,tot;
    int right[maxn],in[maxn];
    void init()
    {
        last=tot=0;
        memset(ch[0],-1,sizeof(ch[0]));
        pre[0]=-1;
        step[0]=0;
    }
    void add(int c)
    {
        c-='a';
        int p=last,np=++tot;
        step[np]=step[p]+1;
        memset(ch[np],-1,sizeof(ch[np]));
        right[np]=1;
        while(~p&&ch[p][c]==-1) ch[p][c]=np,p=pre[p];
        if(p==-1) pre[np]=0;
        else{
            int q=ch[p][c];
            if(step[q]!=step[p]+1){
                int nq=++tot;
                memcpy(ch[nq],ch[q],sizeof(ch[q]));
                step[nq]=step[p]+1;
                pre[nq]=pre[q];
                pre[q]=pre[np]=nq;
                while(~p&&ch[p][c]==q) ch[p][c]=nq,p=pre[p];
            }
            else pre[np]=q;
        }
        last=np;
    }
    void calc()
    {
        MS0(in);
        REP(i,1,tot) in[pre[i]]++;
        queue<int> q;
        REP(i,1,tot) if(!in[i]) q.push(i);
        while(!q.empty()){
            int u=q.front();q.pop();
            if(pre[u]==-1) continue;
            right[pre[u]]+=right[u];
            if(--in[pre[u]]==0) q.push(pre[u]);
        }
        MS0(ans);
        REP(i,1,tot) ans[step[i]]=max(ans[step[i]],right[i]);
    }
};SAM sam;

void solve()
{
    sam.init();
    int len=strlen(s);
    REP(i,0,len-1) sam.add(s[i]);
    sam.calc();
    for(int i=len-1;i>=1;i--) ans[i]=max(ans[i],ans[i+1]);
    REP(i,1,len) printf("%d\n",ans[i]);
}

int main()
{
    freopen("in.txt","r",stdin);
    while(~scanf("%s",s)){
        solve();
    }
    return 0;
}
View Code

spoj SUBLEX

屢次詢問一個串字典序第k大的子串,先dp遞推出個數,而後直接dfs查找就好了。

#include<bits/stdc++.h>
#define REP(i,a,b) for(int i=a;i<=b;i++)
#define MS0(a) memset(a,0,sizeof(a))

using namespace std;

typedef long long ll;
const int maxn=2000100;
const int INF=1e9+10;

char s[maxn];
int n;ll k;
char ans[maxn];int ansn;
struct SAM
{
    int ch[maxn][26];
    int pre[maxn],step[maxn];
    int last,tot;
    ll dp[maxn];
    void init()
    {
        last=tot=0;
        memset(ch[0],-1,sizeof(ch[0]));
        pre[0]=-1;
        step[0]=0;
        memset(dp,-1,sizeof(dp));
    }
    void add(int c)
    {
        c-='a';
        int p=last,np=++tot;
        step[np]=step[p]+1;
        memset(ch[np],-1,sizeof(ch[np]));
        while(~p&&ch[p][c]==-1) ch[p][c]=np,p=pre[p];
        if(p==-1) pre[np]=0;
        else{
            int q=ch[p][c];
            if(step[q]!=step[p]+1){
                int nq=++tot;
                memcpy(ch[nq],ch[q],sizeof(ch[q]));
                step[nq]=step[p]+1;
                pre[nq]=pre[q];
                pre[q]=pre[np]=nq;
                while(~p&&ch[p][c]==q) ch[p][c]=nq,p=pre[p];
            }
            else pre[np]=q;
        }
        last=np;
    }
    ll dfs(int u)
    {
        ll &res=dp[u];
        if(~res) return res;
        res=1;
        REP(c,0,25){
            if(~ch[u][c]) res+=dfs(ch[u][c]);
        }
        return res;
    }
    void find(int u,ll k)
    {
        if(u) k--;
        if(k<=0) return;
        REP(c,0,25){
            int v=ch[u][c];
            if(~v){
                ll tmp=dfs(v);
                if(k-tmp<=0){
                    ans[ansn++]=c+'a';
                    find(v,k);
                    return;
                }
                k-=tmp;
            }
        }
    }
};SAM sam;

void solve()
{
    sam.init();
    int len=strlen(s);
    REP(i,0,len-1) sam.add(s[i]);
    REP(i,1,n){
        scanf("%d",&k);
        ansn=0;
        sam.find(0,k);
        ans[ansn]='\0';
        puts(ans);
    }
}

int main()
{
    freopen("in.txt","r",stdin);
    scanf("%s",s);
    scanf("%d",&n);
    solve();
    return 0;
}
View Code

 -------------------------

關於LCT維護SAM,目的就是在線維護right數組,就是用LCT去在線維護parent樹,其實就是在線維護right集合的時候須要在鏈接結點u和pre[u]維護parent樹時須要在u到根的路徑上的結點的right值所有+1,而後鏈接結點涉及刪邊加邊,因此用LCT。。。例題,bzoj2555。

bzoj2555:

如上,用LCT去維護parent來實如今線維護SAM,調了一下午。。。

#include<bits/stdc++.h>
#define REP(i,a,b) for(int i=a;i<=b;i++)
#define MS0(a) memset(a,0,sizeof(a))

using namespace std;

typedef long long ll;
const int maxn=3200100;
const int INF=1e9+10;

char s[maxn];
int len;
char op[20];
int Q;

struct LCT
{
    int pre[maxn],ch[maxn][2],rev[maxn];
    int val[maxn],add[maxn];
    void init()
    {
        MS0(pre);MS0(ch);MS0(rev);MS0(add);
        MS0(val);
    }
    void update_add(int x,int w)
    {
        if(!x) return;
        val[x]+=w;
        add[x]+=w;
    }
    void update_rev(int x)
    {
        if(!x) return;
        swap(ch[x][0],ch[x][1]);
        rev[x]^=1;
    }
    void down(int x)
    {
        if(add[x]){
            update_add(ch[x][0],add[x]);
            update_add(ch[x][1],add[x]);
            add[x]=0;
        }
        if(rev[x]){
            update_rev(ch[x][0]);
            update_rev(ch[x][1]);
            rev[x]=0;
        }
    }
    bool isroot(int x)
    {
        return ch[pre[x]][0]!=x&&ch[pre[x]][1]!=x;
    }
    void up(int x)
    {

    }
    void P(int x)
    {
        if(!isroot(x)) P(pre[x]);
        down(x);
    }
    void rot(int x,int kind)
    {
        int y=pre[x];
        ch[y][kind^1]=ch[x][kind];
        pre[ch[x][kind]]=y;
        if(!isroot(y)) ch[pre[y]][ch[pre[y]][1]==y]=x;
        pre[x]=pre[y];
        ch[x][kind]=y;
        pre[y]=x;
        up(y);
    }
    void splay(int x)
    {
        P(x);
        while(!isroot(x)){
            if(isroot(pre[x])) rot(x,ch[pre[x]][0]==x);
            else{
                int y=pre[x],z=pre[y];
                int kind=ch[y][0]==x,one=0;
                if(ch[y][0]==x&&ch[z][0]==y) one=1;
                if(ch[y][1]==x&&ch[z][1]==y) one=1;
                if(one) rot(y,kind),rot(x,kind);
                else rot(x,kind),rot(x,kind^1);
            }
        }
        up(x);
    }
    int access(int x)
    {
        int t=0;
        while(x){
            splay(x);
            ch[x][1]=t;t=x;x=pre[x];
            up(t);
        }
        return t;
    }
    void makeroot(int x)
    {
        access(x);splay(x);update_rev(x);
    }
    void ADD(int x,int y,int w)
    {
        makeroot(x);access(y);splay(y);
        update_add(y,w);
    }
    void cut(int x,int y)
    {
        makeroot(x);access(y);splay(y);ch[y][0]=pre[x]=0;up(y);
        ADD(y,1,-val[x]);
    }
    void link(int x,int y)
    {
        makeroot(x);pre[x]=y;up(y);
        ADD(y,1,val[x]);
    }
    int query(int x)
    {
        splay(x);
        return val[x];
    }
};LCT lct;

struct SAM
{
    int ch[maxn][26];
    int pre[maxn],step[maxn];
    int last,tot;
    int newnode(int k)
    {
        ++tot;
        step[tot]=k;
        MS0(ch[tot]);
        pre[tot]=0;
        return tot;
    }
    void init()
    {
        tot=0;
        last=newnode(0);
        lct.init();
    }
    void add(int c)
    {
        c-='A';
        int p=last,np=newnode(step[p]+1);
        lct.val[np]=1;
        while(p&&ch[p][c]==0) ch[p][c]=np,p=pre[p];
        if(p==0) pre[np]=1,lct.link(np,1);
        else{
            int q=ch[p][c];
            if(step[q]!=step[p]+1){
                int nq=newnode(step[p]+1);
                memcpy(ch[nq],ch[q],sizeof(ch[q]));
                lct.cut(q,pre[q]);
                lct.link(nq,pre[q]);
                lct.link(q,nq);
                lct.link(np,nq);
                pre[nq]=pre[q];
                pre[q]=pre[np]=nq;
                while(p&&ch[p][c]==q) ch[p][c]=nq,p=pre[p];
            }
            else pre[np]=q,lct.link(np,q);
        }
        last=np;
    }
    int find(char *s)
    {
        int len=strlen(s);
        int u=1;
        REP(i,0,len-1){
            int c=s[i]-'A';
            if(ch[u][c]) u=ch[u][c];
            else return 0;
        }
        return lct.query(u);
    }
};SAM sam;

void decode(char *s,int mask)
{
    int len=strlen(s);
    REP(i,0,len-1){
        mask=(mask*131+i)%len;
        swap(s[i],s[mask]);
    }
}

int main()
{
    freopen("in.txt","r",stdin);
    while(~scanf("%d",&Q)){
        scanf("%s",s);
        len=strlen(s);
        sam.init();
        REP(i,0,len-1) sam.add(s[i]);
        int mask=0;
        REP(i,1,Q){
            scanf("%s%s",op,s);
            decode(s,mask);
            if(op[0]=='A'){
                len=strlen(s);
                REP(i,0,len-1) sam.add(s[i]);
            }
            else{
                int res=sam.find(s);
                printf("%d\n",res);
                mask^=res;
            }
        }
    }
    return 0;
}
View Code

---------------------------

相關文章
相關標籤/搜索