zoukankan      html  css  js  c++  java
  • 后缀自动机专题

    如果不算pre指针的话后缀自动机就是一个DAG,这是它能很方便地进行dp的前提。

    而pre指针返回什么呢,返回的就是上一个的前缀包含改结点所代表子串的那个后缀,和AC自动机上的fail指针很像,都是为了匹配。我目前学得不深,看不出和AC自动机的fail指针有什么区别,用起来也几乎一样。

    相比于字典树和回文树,后缀自动机每个结点会有多个父结点,可以表示多种子串(从根节点到它的每条路径都是一个子串),因此子串的信息只能在路径中记录,比如长度,而该子串说记录的长度step,则是根结点到它的最远距离,而某个子串的长度就是该代表该子串的路径的长度了,并不一定是某个结点的step。

    spoj1811

    求两个串的最长公共子串的长度。

    对A串建立后缀自动机,对B串进行匹配,如果匹配失败,沿着失败指针往回走到第一个能匹配的位置继续匹配(看起来似曾相识?没错,这不是AC自动机的过程吗。。。),当然如果到根节点还不能继续匹配,那就只有从头再来了。这里随时记录长度更新答案即可。

    当然后缀数组也可以做,把两个串拼接起来,求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
    ",sam.find(t));
    }
    
    int main()
    {
        freopen("in.txt","r",stdin);
        while(~scanf("%s%s",s,t)){
            solve();
        }
        return 0;
    }
    View Code

     spoj1812

    求多个串的最长公共子串的长度。

    对第一个串建立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
    ",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
    ",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]='';
            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
    ",res);
                    mask^=res;
                }
            }
        }
        return 0;
    }
    View Code

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

    没有AC不了的题,只有不努力的ACMER!
  • 相关阅读:
    到达波密
    福建
    到达拉萨
    樟木半天游
    修改控制寄存器GPBCON
    WINCE实现屏幕旋转的方法
    6410主频
    如何解决触摸屏抖动问题
    视频格式D1
    安家了
  • 原文地址:https://www.cnblogs.com/--560/p/5457023.html
Copyright © 2011-2022 走看看