zoukankan      html  css  js  c++  java
  • AC自动机 后缀数组 随笔

    天天一头雾水,真是头大呢;

    也祝自己生日快乐7.12,真是和海亮美好的回忆;

    AC自动机:自动A题的机器;显然这样说是错的;

    这个东西 我只想写一下自己的理解 然后一些总结;

    首先 它是具有KMP性质的东西,但是又是有区别的;

    KMP的Next数组是用来最长公共前后缀,而AC自动机的fail指针是用来搞相同后缀即可;

    因为KMP只对一个模式串做匹配,而AC自动机要对多个模式串做匹配。

    有可能 fail指针指向的结点对应着另一个模式串,两者前缀不同。

    也就是说,AC自动机在对匹配串做逐位匹配时,同一位上可能匹配多个模式串。

    因此 fail 指针会在字典树上的结点来回穿梭,而不像KMP在线性结构上跳转。

    首先是将模式串插入trie树中,插入操作一样,注意打标记;

    我们利用部分已经求出 fail 指针的结点推导出当前结点的 fail 指针。具体我们用BFS实现:

    考虑字典树中当前的节点u,u的父节点是p,p通过字符c的边指向u。

    假设深度小于u的所有节点的 fail指针都已求得。那么p的 fail指针显然也已求得。

    我们跳转到p的 fail指针指向的结点 fail[p] ;

    如果结点 fail[p]通过字母 c 连接到的子结点 w 存在:

    则让u的fail指针指向这个结点 w ( fail[u]=w)。

    相当于在 p 和 fail[p] 后面加一个字符 c ,就构成了 fail[u] 。

    如果 fail[p]通过字母 c 连接到的子结点 w 不存在:

    那么我们继续找到 fail[fail[p]] 指针指向的结点,重复上述判断过程,一直跳 fail 指针直到根节点。

    如果真的没有,就令 fail[u]= 根节点。

    然后建出AC自动机,搞出每个的fail指针,然后拿文本串在上面跑一下就行了;

    模板一

    #include<bits/stdc++.h>
    using namespace std;
    
    template<typename T>inline void read(T &x) {
        x=0;
        register int f=1;
        register char ch=getchar();
        while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }
    
    const int N=5000010;
    int n,m,trie[N][26],fail[N],e[N],idx;
    char s[1000005];
    
    inline void insert(char *s) {
        int p=0;
        for(int i=0;s[i];i++) {
            int ch=s[i]-'a';
            if(!trie[p][ch]) trie[p][ch]=++idx;
            p=trie[p][ch];
        }
        e[p]++;
    }
    
    inline void build() {
        queue<int> q;
        memset(fail,0,sizeof(fail));
        for(int i=0;i<26;i++) if(trie[0][i]) q.push(trie[0][i]);
        while(q.size()) {
            int k=q.front(); q.pop();
            for(int i=0;i<26;i++) {
                if(trie[k][i]) {
                    fail[trie[k][i]]=trie[fail[k]][i];
                    q.push(trie[k][i]);
                }
                else trie[k][i]=trie[fail[k]][i];
            }
        }
    } 
    
    int query(char *t) {
        int p=0,res=0;
        for(int i=0;t[i];i++) {
            p=trie[p][t[i]-'a'];
            for(int j=p;j&&~e[j];j=fail[j])res+=e[j],e[j]=-1;
        }
        return res;
    }
    
    int main() {
        read(n);
        for(int i=1;i<=n;i++) {
            scanf("%s",s);
            insert(s);
        }
        build();
        scanf("%s",s);
        int ans=query(s);
        printf("%d
    ",ans);
    } 
    View Code

    模板二:

    #include<bits/stdc++.h>
    using namespace std;
    template<typename T>inline void read(T &x) {
        x=0;
        register int f=1;
        register char ch=getchar();
        while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }
    string mob[300010];
    int e[300010],trie[300010][26],fail[300010],ans[300010],n,idx;
    
    inline void insert(string s,int v) {
        int p=0;
        for(int i=0;i<s.size();i++) {
            int ch=s[i]-'a';
            if(!trie[p][ch]) trie[p][ch]=++idx;
            p=trie[p][ch];
        }
        e[p]=v;
    }
    
    inline void build() {
        queue<int> q;
    //    memset(fail,0,sizeof(fail));
        for(int i=0;i<26;i++) if(trie[0][i]) q.push(trie[0][i]);
        while(q.size()) {
            int k=q.front(); q.pop();
            for(int i=0;i<26;i++) {
                if(trie[k][i]) {
                    fail[trie[k][i]]=trie[fail[k]][i];
                    q.push(trie[k][i]);
                }
                else trie[k][i]=trie[fail[k]][i];
            }
        }
    } 
    
    inline void query(string s) {
        int p=0;
        for(int i=0;i<s.size();i++){
            p=trie[p][s[i]-'a'];
            for(int j=p;j;j=fail[j])    ans[e[j]]++;
        }
    }
    
    int main() {
    //    freopen("a.in","r",stdin);
    //    freopen("a.out","w",stdout);
        while(scanf("%d",&n),n) {
            idx=0;
    //        clear();
            memset(e,0,sizeof(e));
            memset(ans,0,sizeof(ans));
            memset(trie,0,sizeof(trie));
            memset(fail,0,sizeof(fail));
            for(int i=1;i<=n;i++) {
                cin>>mob[i];
                insert(mob[i],i);
            }
            build();
            string t;
            cin>>t;
            query(t);
            int temp=0;
            for(int i=1;i<=n;i++)if(ans[i]>temp)    temp=ans[i];
            cout<<temp<<endl;
            for(int i=1;i<=n;i++)if(ans[i]==temp)    cout<<mob[i]<<"
    ";
        }
        return 0;
    }
    View Code

    模板三:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=5000010;
    template<typename T>inline void read(T &x) {
        x=0;
        register int f=1;
        register char ch=getchar();
        while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }
    int n,idx,lin[200010],tot;
    int trie[200010][26],fail[200010],e[200010],size[200010];
    string s,t;
    int ans[200010];
    
    struct gg {
        int x,y,next;
    }a[N<<1];
    
    inline void insert(string s,int v) {
        int p=0;
        for(int i=0;i<s.size();i++) {
            int ch=s[i]-'a';
            if(!trie[p][ch]) trie[p][ch]=++idx;
            p=trie[p][ch];
        }
        e[v]=p;
    }
    
    inline void build() {
        queue<int> q;
        memset(fail,0,sizeof(fail));
        for(int i=0;i<26;i++) if(trie[0][i]) q.push(trie[0][i]);
        while(q.size()) {
            int k=q.front(); q.pop();
            for(int i=0;i<26;i++) {
                if(trie[k][i]) {
                    fail[trie[k][i]]=trie[fail[k]][i];
                    q.push(trie[k][i]);
                }
                else trie[k][i]=trie[fail[k]][i];
            }
        }
    } 
    
    inline void add(int x,int y) {
        a[++tot].y=y; a[tot].next=lin[x]; lin[x]=tot;
    }
    
    inline void dfs(int x) {
        for(int i=lin[x];i;i=a[i].next) {
            int y=a[i].y;
            dfs(y);
            size[x]+=size[y];
        }
    }
    
    int main() {
    //    freopen("a.in","r",stdin);
    //    freopen("a.out","w",stdout);
        read(n);
        for(int i=1;i<=n;i++) {
            cin>>s;
            insert(s,i);
        } 
        build();
        cin>>t;
        int p=0;
        for(int i=0;i<t.size();i++) {
            p=trie[p][t[i]-'a'];
            size[p]++;
        }
        for(int i=1;i<=idx;i++) add(fail[i],i);//建个fail树 
        dfs(0);
        for(int i=1;i<=n;i++) printf("%d
    ",size[e[i]]);
        return 0;
    }
    View Code

    来几道比较有意思的题目:

    病毒:

    这个题目没有文本串,我们可以想象出一个符合条件的文本串,如果存在,那么这个文本串一定不能在病毒串组成的trie上匹配,那么他一定会沿着fail指针一直跳,

    怎么才能一直跳,有环,对啊,并且这里容易想明白的是,我们一定不能跳到一个有结束标记的节点;

    但是直接写的话,我们一定会失败,为什么,我们考虑一种情况,如果在跳fail指针的时候,我们顺着fail指针调到一个有结束节点的地方,那么我们也是不能选择这个跳,

    我们把这些情况预处理出来就可以了;dfs找环是否存在就好了;

    #include<bits/stdc++.h>
    using namespace std;
    template<typename T>inline void read(T &x)
    {
        x=0;
        register int f=1;
        register char ch=getchar();
        while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }
    
    int n,idx,flag,trie[3000010][26],e[3000010],fail[3000010],vis[3000010];
    string s;
    
    inline void insert(string s) {
        int p=0;
        for(int i=0;i<s.size();i++) {
            int ch=s[i]-'0';
            if(!trie[p][ch])  trie[p][ch]=++idx;
            p=trie[p][ch];
        }
        e[p]=1;
    }
    
    inline void build() {
        queue<int> q;
        memset(fail,0,sizeof(fail));
        for(int i=0;i<10;i++) 
            if(trie[0][i]) q.push(trie[0][i]);
        while(q.size()) {
            int k=q.front();q.pop();
            for(int i=0;i<10;i++) {
                if(trie[k][i])  {
                    fail[trie[k][i]]=trie[fail[k]][i];
                    q.push(trie[k][i]);
                }
                else trie[k][i]=trie[fail[k]][i];
            }
        }
    }
    
    inline void dfs(int x) {
        if(flag) return ;
        for(int i=0;i<=1;i++) {
            int p=trie[x][i];
            if(!vis[p]&&!e[p]) {
                vis[p]=1;
                dfs(p);
                vis[p]=0;//还原现场; 
            }
            else if(vis[p]&&!e[p]) {
                flag=1;
                return ;
            }
        }
    }
    
    int main() {
    //    freopen("a.in","r",stdin);
    //    freopen("a.out","w",stdout);
        read(n);    
        for(int i=1;i<=n;i++) {
            cin>>s;
            insert(s);
        } 
        build();
        vis[0]=1;
        for(int i=1;i<=idx;i++) {
            int k=i,m=0;
            while(k>0) {
                if(e[k])
                m=k;
                k=fail[k];
            }
            k=i;
            if(m==0) continue;
            while(k>0) {
                if(k==m) break;
                e[k]=1;
                k=fail[k];
            }
        }
        dfs(0);
        if(flag) {
            printf("TAK
    ");
        }
        else {
            printf("NIE
    ");
        }
        return 0;
    }
    View Code

    玄武密码;

    这个题目让我们找能匹配的最长前缀,显然我们预处理出来所有情况,在匹配的时候,当不能匹配的时候退出,此时就是最大长度;

    //#include<bits/stdc++.h>
    #include<iomanip>
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<string>
    #include<queue>
    #include<deque>
    #include<cmath>
    #include<ctime>
    #include<cstdlib>
    #include<stack>
    #include<algorithm>
    #include<vector>
    #include<cctype>
    #include<utility>
    #include<set>
    #include<bitset>
    #include<map>
    #define INF 1000000000
    #define ll long long
    #define min(x,y) ((x)>(y)?(y):(x))
    #define max(x,y) ((x)>(y)?(x):(y))
    #define RI register ll
    #define db double
    #define EPS 1e-8
    using namespace std;
    #define mann 100005
    #define maxn 10000005
    template<typename T>inline void read(T &x) {
        x=0;
        register int f=1;
        register char ch=getchar();
        while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }
    int n,m,idx;
    int trie[maxn][4],fail[maxn],e[maxn],f[maxn];
    char s[maxn],d[4],b[mann][105];
    
    inline char change(char c)
    {
        if(c=='E') return d[0];
        else if(c=='S') return d[1];
        else if(c=='W') return d[2];
        else return d[3];
    }
    
    
    inline void insert(char *s) {
        int p=0,len=strlen(s);
        for(int i=0;i<len;i++) {
            int ch=s[i]-'a';
            if(!trie[p][ch])  trie[p][ch]=++idx;
            p=trie[p][ch];
        }
        e[p]=1;
    }
    
    inline void build() {
        queue<int> q;
        memset(fail,0,sizeof(fail));
        for(int i=0;i<4;i++) if(trie[0][i]) q.push(trie[0][i]);
        while(q.size()) {
    //    cout<<"((()))"<<endl; 
            int k=q.front(); q.pop();
            for(int i=0;i<4;i++) {
                if(trie[k][i]) {
                    fail[trie[k][i]]=trie[fail[k]][i];
                    q.push(trie[k][i]);
                }
                else trie[k][i]=trie[fail[k]][i];
            }
        }
    } 
    
    inline void query(char *s) {
        int len=strlen(s),p=0,k;
        for(int i=0;i<len;i++) {
            int ch=s[i]-'a';
            k=trie[p][ch];
            while(k>0) {
                if(f[k]) break;//不用再跳fail了,已被更新; 
                f[k]=1;
                k=fail[k];
            }
            p=trie[p][ch];
        }
    }
    
    inline int ask(char *t) {
        int len=strlen(t),p=0;
        for(int i=0;i<len;i++) {
            int ch=t[i]-'a';
            p=trie[p][ch];
            if(!f[p]) return i;
        }
        return len;
    }
    
    int main() {
    //    freopen("1.in","r",stdin);
    //    freopen("a.out","w",stdout);
        d[0]='a',d[1]='b',d[2]='c',d[3]='d';
        read(n); read(m);
        scanf("%s",s);
        for(int i=0;i<n;i++) {
            s[i]=change(s[i]);
        } 
        for(int i=1;i<=m;i++) {
            scanf("%s",b[i]);
            int len=strlen(b[i]);
            for(int j=0;j<len;j++)
                b[i][j]=change(b[i][j]);
            insert(b[i]);
        }
        build();
        query(s);
        for(int i=1;i<=m;i++) {
            cout<<ask(b[i])<<endl;
        } 
        return 0;
    }
    View Code

    阿狸打字机,最近会做;

    然后谈谈我对后缀数组的小理解,可能会有错误,以后会订正;

    后缀排序;

    反正我也刚学这个东西,后缀自动机学了一点没来得及写题,一说到这里,我就想起来自己线性代数没写,数据结构没写,数论没写,啊啊啊;

    算了,首先后缀数组(SA)是个好东西啊;

    我们用基数排序是在O(n)的时间复杂度内对每个后缀进行排序;

    倍增合并的话是logn的,总的复杂度变成了nlogn,比快排少一个log,还不错;

    就是两个关键字搞来搞去,注释在代码里;

    sa数组表示排名为i的后缀的起始下标;也就是这道题所求的答案;

    x数组是第一关键字,y数组是第二关键字,c数组是桶;

    我们先将x扔进桶里,排好之后将y扔进去;’

    heigh数组表示排名为i的后缀和排名为i-1的后缀的lcp,(最长公共前缀);

    设h[i]=height[rk[i]],同样的,height[i]=h[sa[i]];

    有定理:h[i]>=h[i-1]-1;

    不过这就是我以后写题后再来填坑吧;

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1000010; 
    template<typename T>inline void read(T &x) {
        x=0;
        register int f=1;
        register char ch=getchar();
        while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }
    
    int n,m,c[N],sa[N],x[N],y[N];
    char s[N];
    
    inline void get_sa() {
        m=122;
        for(int i=1;i<=n;i++) c[x[i]=s[i]]++;//建基数排序的桶 ,x[i]为i个字符的第一关键字 
        for(int i=2;i<=m;i++) c[i]+=c[i-1];// 做c的前缀和,为了求出每个关键字最多在第几名; 
        for(int i=n;i>=1;i--) sa[c[x[i]]--]=i;
        for(int k=1;k<=n;k=k<<1) {
            int num=0;//一个计数器; 
            for(int i=n-k+1;i<=n;i++) y[++num]=i;//y表示是第二关键字;
            //因为n-k+1位到第n位是空串,空串优先级最高,排名靠前 
            for(int i=1;i<=n;i++) if(sa[i]>k) y[++num]=sa[i]-k;
            //如果排名为i的数 在数组中是否大于k;
            //如果(sa[i]>k) 他可以作为第二关键字,直接把它第一关键字的位置添加y即可;
            //这里i枚举的是排名,第二关键字靠前的先进; 
            for(int i=1;i<=m;i++) c[i]=0;//清桶; 
            for(int i=1;i<=n;i++) ++c[x[i]];// 此时x作为第一关键字已经更新过,直接丢尽桶; 
            for(int i=2;i<=m;i++) c[i]+=c[i-1];//第一关键字排名为1-i的个数; 
            for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
            //因为y是按照第二关键字排的,所以第二关键字靠后的,在第一关键字对应的桶中靠后; 
            swap(x,y);//无脑交换 ,利用上次信息倍增向下走; 
            x[sa[1]]=1,num=1;
            for(int i=2;i<=n;i++)
                x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            if(num==n) break;
            m=num;//此时不需要122了; 
        }
        for(int i=1;i<=n;i++) printf("%d ",sa[i]);
    }
    
    int main() {
    //    freopen("a.in","r",stdin);
    //    freopen("a.out","w",stdout);
        scanf("%s",s+1);
        n=strlen(s+1);
        get_sa();
    //    get_height();
        return 0;
    }
  • 相关阅读:
    ssh框架整合
    spring事务管理
    spring AOP
    spring静态代理和动态代理
    log4j介绍
    Socket通信介绍
    C# 串口与窗体应用程序的连接
    Halcon中的图像相减算子abs_diff_image和sub_image
    R-CNN、fast-RCNN、faster-RCNN到yolo、SSD简要
    QT入门系列(2):MinGW与MSVC编译的区别
  • 原文地址:https://www.cnblogs.com/Tyouchie/p/11182025.html
Copyright © 2011-2022 走看看