AC自动机用于在主串中,找多个模板串的出现次数。不论是出现次数只记1次,还是几多次,都可。
AC自动机其实就是模板串构成的Trie图加上fail边,有点像kmp里的Next数组,具体解析先放下,没时间,下面是板子。

e对了,最近的东西确实有难度,然额对于稍微难一点的东西,网上的板子都是那种参差不齐的,乱设变量,写的只有自己看得懂,正确性还不绝对保证,变量也不说明都是干啥用的,看的气愤。。
所以,最重要的还是先解释代码中的变量都是干啥用的,啥功能,名字尽可能规范吧。
Trie用来存树(图),fail[i]表示编号为i的结点指向的结点,val[i]表示编号为i的结点是几个字符串的终点。cnt表示Trie树中已经有多少个结点了。这里再说明一下,Trie树正解应该是数据(字符)都是在边上存的,点的值为编号。

typedef struct AC_Auto{
    int Trie[maxn][26],fail[maxn],val[maxn],cnt;
    void insert(char *s,int len);    //要插入的字符串以及长度
    void build();                    //构建自动机,不需要参数
    int query(char *s,int len);     //根据实际需要写
    void clear();
}AC;
void AC::insert(char *s,int len){
    int root = 0;                   //插入当然是从根节点开始啦
    for (int i=1;i<=len;i++){
        if (!Trie[root][s[i]-'a'+1])  Trie[root][s[i]-'a'+1] = ++cnt;
        root = Trie[root][s[i]-'a'+1];
    }
    val[root]++;                  //终止结点标注一下。
    return;
}
void AC::build(){
    queue<int> q;
    for (int i=1;i<=26;i++)  if (Trie[0][i])  //与根节点直接相连的结点fail边都是直接指向根的,那么入队就行
        q.push(Trie[0][i]);
    while (!q.empty()){
        int temp = q.front();   q.pop();
        for (int i=1;i<=26;i++){
            if (Trie[temp][i]){
                fail[Trie[temp][i]] = Trie[fail[temp]][i];
                q.push(Trie[temp][i]);
            }else{
                Trie[temp][i] = Trie[fail[temp]][i];
            }
        }
    }
    return;
}
int AC::query(char *s,int len){
    int ans = 0;
    int root = 0,temp;
    for (int i=1;i<=len;i++){         //i是扫描主串的,root是扫描Trie图的,与kmp也相似。一个扫描主串,一个扫描模板串。
        root = Trie[root][s[i]-'a'+1];
        temp = root;
        while (root){
            ans += val[root];
            root = fail[root];
        }
    }
    return ans;
}
void AC::clear(){
    cnt = 0;
    memset(Trie,0,sizeof(Trie));
    memset(fail,0,sizeof(fail));
    memset(val,0,sizeof(val));
}

AC自动机不支持在线操作,如果中途插入其他的串,就必须再次build。

emmmm..板子确实长,在注意几点,上面在连fail边的时候,若root的某个子结点存在,那么fail边直接连上root的fail的子结点,然后入队;若不存在,这里优化一下,不存在就不连fail边了,直接把Trie[root][s[i]-'a'+1]赋值为root的fail的子结点,这样,在匹配主串的时候,可以直接当成该子结点存在去匹配,与query()中的root = Trie[root][s[i]-'a'+1];是对上的,这里要再好好想想!

补充:这个板子的query是计算每个模板串出现多次的板子。

最新的板子(topu排序)
洛谷P5357 AC自动机 二次加强版

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int maxn = 2e5+5,maxn2 = 2e6+5;
typedef struct AC_Auto{
    int Trie[maxn][26],fail[maxn],val[maxn],cnt;
    int ans[maxn],cnt2,mp[maxn],in[maxn];
    int times[maxn];                  //表示某个结点出现了多少次
    AC_Auto(){
        cnt = cnt2 = 0;
        memset (times,0,sizeof times);
    }
    void insert(char *s,int len,int flag);    //要插入的字符串以及长度
    void build();                    //构建自动机,不需要参数
    int query(char *s,int len);     //根据实际需要写
    void clear();
    void output();
    void topu();
}AC;
char s[maxn2];
int n,len;
AC a;
int main(){
    scanf ("%d",&a.cnt2);
    for (int i=1;i<=a.cnt2;i++){
        scanf ("%s",s+1);
        a.insert(s,strlen(s+1),i);
    }
    scanf ("%s",s+1);
    a.build();
    a.query(s,strlen(s+1));
    a.topu();
    a.output();
    return 0;
}

void AC::insert(char *s,int len,int flag){
    int root = 0;                   //插入当然是从根节点开始啦
    for (int i=1;i<=len;i++){
        if (!Trie[root][s[i]-'a'+1])  Trie[root][s[i]-'a'+1] = ++cnt;
        root = Trie[root][s[i]-'a'+1];
    }
    val[root]++;                  //终止结点标注一下。
    mp[flag] = root;
    return;
}
void AC::build(){
    queue<int> q;
    for (int i=1;i<=26;i++)  if (Trie[0][i])  //与根节点直接相连的结点fail边都是直接指向根的,那么入队就行
        q.push(Trie[0][i]);
    while (!q.empty()){                       //从队列里面拿出来一个元素temp,然后开始对temp的孩子结点连fail边
        int temp = q.front();   q.pop();
        for (int i=1;i<=26;i++){
            if (Trie[temp][i]){
                fail[Trie[temp][i]] = Trie[fail[temp]][i];
                in[Trie[fail[temp]][i]]++;
                q.push(Trie[temp][i]);
            }else{
                Trie[temp][i] = Trie[fail[temp]][i];
            }
        }
    }
    return;
}
int AC::query(char *s,int len){
    int root = 0,temp;
    for (int i=1;i<=len;i++){         //i是扫描主串的,root是扫描Trie图的,与kmp也相似。一个扫描主串,一个扫描模板串。
        root = Trie[root][s[i]-'a'+1];
        times[root]++;
    }
    return 0;
}
void AC::topu(){
    queue<int> q;
    int temp;
    for (int i=1;i<=cnt;i++)    if (in[i]==0)
        q.push(i);
    while (!q.empty()){
        temp = q.front();   q.pop();
        times[fail[temp]] += times[temp];
        in[fail[temp]]--;
        if (in[fail[temp]]==0)
            q.push(fail[temp]);
    }
    return;
}

void AC::output(){
    for (int i=1;i<=cnt2;i++)
        printf ("%d\n",times[mp[i]]);
    return;
}
void AC::clear(){
    cnt = cnt2 = 0;
    memset(Trie,0,sizeof(Trie));
    memset(fail,0,sizeof(fail));
    memset(val,0,sizeof(val));
    memset(ans,0,sizeof(ans));
    memset(mp,0,sizeof(mp));
}

0 条评论

发表评论

邮箱地址不会被公开。