AC自动机 算法模板

这是网课的配套代码哦~ 戳我
多模式匹配

通过公共后缀来进行匹配失败时候的跳转 因为模式串在字符串中可重叠出现

利用了trie树和类KMP的思想 可以近似看作在trie树上的kmp匹配

复杂度 O ( n ) O(n) O(n)

构建fail指针

  • fail本质是当前pattern的最长后缀

  • 第一层的全部指向root

  • 通过BFS遍历后面的节点 因为跳转是从长到短的

  • 如果当前节点x的父节点f的fail指针拥有和当前节点一样字符的节点y 那么x的fail指向y

匹配

  • 从根节点出发
  • 正常的trie树匹配过程 遇到节点就把以该节点为结尾的模式串添加计数到答案中
    • 每次添加计数都要把该节点的fail链上的计数都遍历添加了
    • 如 s: abcde p: abcde bcde cde de e 那么在匹配到e的时候应该要通过fail链把五个都加上
  • 匹配失败的时候 (没有了相同字符的孩子)
    • 进行跳转 如果跳转后的有相同字符的孩子就进入它
    • 直到进入了或者到根节点了
  • 直到字符串遍历完成

给定n个pattern 和 s 求s中出现了多少个pattern (重复的不算)

patterns的字符总长为m 只有小写字母

const int n;
const int m;	//用模式串来建树 m其实就是节点数
vector<string> patterns(n);	//模式串
string s;	//文本串
struct TrieNode{
    int son[26]={0};	//此处只有小写字母
    int cnt=0;
    int fail=-1;	//-1表示没有fail指针
};
vector<TrieNode> trie(m);	//数组模拟 预先分配空间
int idx=0;
void insert(string s)	//trie的插入 详见字典树
{
    int p=0;
    for(int i=0;i<s.size();i++)
    {
        int word=s[i]-'a';
        if(!trie[p].son[word])trie[p].son[word]=++idx;
        p=trie[p].son[word];
    }
    trie[p].cnt++;
}
void fail_pre()
{
    queue<int> q;	//BFS预处理
    for(int i=0;i<26;i++)	//先把第一层的处理掉
    	if(trie[0].son[i]){
            trie[trie[0].son[i]].fail=0;	//第一层的fail全部指向根节点
            q.push(trie[0].son[i]);	//压入队列bfs
        }
    while(q.size())
    {
        int f=q.front();	//拿出一个节点 这个节点的那层fail已处理
        q.pop();
        for(int i=0;i<26;i++)
        	if(trie[f].son[i]){	//遍历所有儿子
                int now=trie[f].son[i];	//儿子的索引
                int ffail=trie[f].fail;	//父亲的fail指向的那个节点
                while(~ffail&&!trie[ffail].son[i])ffail=trie[ffail].fail;	//终止条件为跳到了root (只有root没有fail) 或者 找到了可以进入的节点
                if(~ffail)trie[now].fail=trie[ffail].son[i];	//如果找到了 连接fail指针
                else trie[now].fail=0;	//没有找到只能指向根节点
                q.push(now);	//记得加入队列继续
            }
    }
}
int query(string s)
{
    int ans=0;
    int p=0;
    for(int i=0;i<s.size();i++)
    {
        int word=s[i]-'a';
        while(!trie[p].son[word]&&~trie[p].fail)p=trie[p].fail;	//如果匹配不到就一直跳转 直到到了根节点(只有根节点没有fail)
        if(trie[p].son[word])
            p=trie[p].son[word];	//如果匹配到了就进入节点
        else continue;	//没有匹配到那此时p肯定在root 可以去匹配下一个了文本字符了
        int p2=p;
        while(~trie[p2].fail&&~trie[p2].cnt){
            //把fail链上的全部都给加上
            ans+=trie[p2].cnt;
            trie[p2].cnt=-1;
            //题目要求 匹配过的串下次不用再匹配了
            //此处置为-1而不是0就可以在一开始就终止整条fail链的跳转
            p2=trie[p2].fail;
        }
    }
    return ans;
}

你可能感兴趣的:(算法,算法,字符串,AC自动机,trie)