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\n",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]<<"\n";
    }
    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\n",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\n");
    }
    else {
        printf("NIE\n");
    }
    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;
}

 

posted @ 2019-07-13 20:53  Tyouchie  阅读(238)  评论(0编辑  收藏  举报