P5357 【模板】AC自动机(二次加强版)(AC自动机建fail树dfs求模式串出现次数)

P5357 【模板】AC自动机(二次加强版)(AC自动机建fail树dfs求模式串出现次数)

传送门

P5357 【模板】AC自动机(二次加强版)(AC自动机建fail树dfs求模式串出现次数)_第1张图片

形式上,AC 自动机基于由若干模式串构成的 Trie 树,并在此之上增加了一些 fail 边;本质上,AC 自动机是一个关于若干模式串的DFA(确定有限状态自动机),接受且仅接受以某一个模式串作为后缀的字符串。 并且,与一般自动机不同的,AC 自动机还有关于某个模式串的接受状态,也就是与某个模式串匹配(以某个模式串为后缀)的那些状态,即某个模式串在 Trie树上的终止节点在 fail 树上的整个子树。

暴力跳 fail 边,会被类似于 aaaaa……aaaaa 这样的串卡掉。

正确的做法是建出 fail 树,记录自动机上的每个状态被匹配了几次,最后求出每个模式串在 Trie 上的终止节点在 fail 树上的子树总匹配次数就可以了,我们dfs建立好的fail树,从当前结点的失配指针指向的结点向该结点连边建fail树,这样我们dfs该树的时候可以保证每一条边 ( u , v ) (u,v) u,v,u到根节点之间的串等于v到根节点之间的串,那么该串的出现次数就是所有的边 ( u , v ) (u,v) u,v,的size之和。不会加重, 因为fail树是由fail指针边构成的,然而所有的点只会建立一个fail指针。例如有三个相同的模式串,只会有两个fail指针。

#include
#include
#include
#include
#include

using namespace std;

const int N = 200010, S = 2000010;

queue<int>q;
struct trie{
    int fail;
    int vis[26];
    int ed;
}ac[N];

int match[N];
int n;
int tot, sizes[N];
int head[N], nex[N], ver[N], cnt, num;

void add(int x, int y){
    ver[cnt] = y;
    nex[cnt] = head[x];
    head[x] = cnt ++ ;
}

void insert(int x, char s[]){
    int len = strlen(s);
    int p = 0;//根从0开始
    for(int i = 0; i < len; ++ i){
        int ch = s[i] - 'a';
        if(!ac[p].vis[ch])
            ac[p].vis[ch] = ++ tot;
        p = ac[p].vis[ch];
    }
    ac[p].ed ++ ;
    match[x] = p;//记录每个模式串在 Trie 树上的终止节点
}

void get_fail(){

    for(int i = 0;i < 26; ++ i){
        if(ac[0].vis[i]){
            ac[ac[0].vis[i]].fail = 0;
            q.push(ac[0].vis[i]);
        }
    }

    while(q.size()){
        int p = q.front();
        q.pop();
        for(int i = 0;i < 26; ++ i){
            if(ac[p].vis[i]){
                ac[ac[p].vis[i]].fail = ac[ac[p].fail].vis[i];
                q.push(ac[p].vis[i]);
            }
            else ac[p].vis[i] = ac[ac[p].fail].vis[i];
        }
    }
}

void dfs(int x){
    for(int i = head[x]; ~i; i = nex[i]){
        int y = ver[i];
        dfs(y);
        sizes[x] += sizes[y];
    }
}

int ac_query(char s[]){

    int p = 0, ans = 0;
    int len = strlen(s);

    for(int i = 0;i < len;++i){//!计算有多少个单词在文本中出现
        p = ac[p].vis[s[i]-'a'];//往下一层走
        for(int k = p;k&&ac[k].ed!=-1;k = ac[k].fail){
                ans += ac[k].ed;
                ac[k].ed = -1;
        }
    }

    p = 0;

    for(int i = 0; i < len; ++ i){
        p = ac[p].vis[s[i] - 'a'];
        sizes[p] ++ ;//!记录匹配次数
    }

    for(int i = 1; i <= tot; ++ i)//!建fail树
        add(ac[i].fail, i);

    dfs(0);//!统计子树和

    for(int i = 1;i <= n; ++ i)
        printf("%d\n", sizes[match[i]]);

    return ans;
}

char s[S];

int main(){
    scanf("%d", &n);
    memset(head, -1, sizeof head);

    for(int i = 1;i <= n; ++ i){
        scanf("%s", s);
        insert(i, s);
    }
    //cout << "ok" << num ++  << endl;
    get_fail();
    //cout << "ok" << num ++  << endl;
    scanf("%s", s);

    //cout << "ok" << num ++  << endl;
    int res = ac_query(s);

    //cout << res << endl;
    return 0;
}

你可能感兴趣的:(#,AC自动机)