AC自动机初学(模板)+ HDU 2222
2018-02-14 19:37
288 查看
AC自动机这个东西,
4000
听起来很高大上,在高中的时候不知道什么是自动机,以为写出了AC自动机就可以自动AC……
现在知道了它是用来解决字符串匹配问题的东西,说白了就是KMP+Trie。这个在去年暑假的时候,hc学长也略微提到过,但是没有具体的讲。
在解决只有一个模式串的匹配问题的时候,我们用朴素的KMP算法即可快速完成,但是,如果有很多个匹配串呢?这是就得用上AC自动机(AC_automation)。大致思路就是,在KMP中,我们失配的时候有一个next数组,表示下一个匹配要把原串往后滑动多少位,也即与最长公前缀有关。现在有多个模式串,那么我滑动的时候就要考虑多个东西的最长公共前缀,对于每一个模式串的前缀都要进行尝试匹配,而且匹配的时候,是要与所有的模式串匹配而不只是单个。所以说,我们先用Trie字典树存储所有的模式串,然后一次扫过所有的模式串。之后每一个点设置一个fail指针,表示失配之后应该匹配前缀相同的另一个模式串的位置。然后类似KMP算法,从头开始匹配,匹配成功则继续,否则滑动到fail的位置继续尝试,直到滑动回了根节点。时间复杂度为O(len),len表示所有串的长度。
首先是构建Trie和fail指针。构建Trie和普通的Trie区别不大,关键是fail指针,这个具体的我就盗别人一张图了,希望不要介意。
这里有abcd、abd、cd和bcd四个模式串,那么我的fail指针就按照图中那么设置,图中只画了c的,可以看到,当串的后缀与fail所指向的串的前缀有公共部分,且这两个一定是最长的。如果是在找不到公共部分,那么就fail指向根节点。具体实现的话,我们通常使用广搜迭代的方法。具体代码如下(以26个小写字母为例): void ins(char* x)
{
int o=root;
for(int k=0;k<strlen(x);k++)
{
int c=x[k]-'a';
if(!T[o].ch[c]) T[o].ch[c]=++tot;
o=T[o].ch[c];
}
T[o].cnt++;
}
void get_fail()
{
queue<int> q;
q.push(root);
while(!q.empty())
{
int o=q.front();
for(int i=0;i<26;i++)
{
if (!T[o].ch[i]) continue;
if (o!=root)
{
int fa=T[o].fail;
while(fa&&!T[fa].ch[i]) fa=T[fa].fail; //迭代fa,使得前缀相等且最后要有当前字母
T[T[o].ch[i]].fail=T[fa].ch[i]; //确定fail指针
} else T[T[o].ch[i]].fail=root;
q.push(T[o].ch[i]);
} q.pop();
}
}
然后就是匹配了。匹配正如之前说的,如果能配上,那么直接往后走,否则就往fail指针走,一直走到root为止。唯一需要注意的是每次匹配上之后,都要走一次fail指针,把所有相互包含的模式串也要计算进去。具体见代码:
大体来说模板部分就是这样的。一个插入、一个求fail指针和匹配。
下面就来说说模板题。 HDU 2222。大致题意就是,先是给出很多个模式串,最后再给出一个字段,然后问这些模式串中有多少个模式串出现在字段中,就是一个AC自动机的裸题。只有一个地方需要改进,那就是每个模式串发现被匹配之后不需要第二次计算,所以直接标记一下即可。具体见代码:
4000
听起来很高大上,在高中的时候不知道什么是自动机,以为写出了AC自动机就可以自动AC……
现在知道了它是用来解决字符串匹配问题的东西,说白了就是KMP+Trie。这个在去年暑假的时候,hc学长也略微提到过,但是没有具体的讲。
在解决只有一个模式串的匹配问题的时候,我们用朴素的KMP算法即可快速完成,但是,如果有很多个匹配串呢?这是就得用上AC自动机(AC_automation)。大致思路就是,在KMP中,我们失配的时候有一个next数组,表示下一个匹配要把原串往后滑动多少位,也即与最长公前缀有关。现在有多个模式串,那么我滑动的时候就要考虑多个东西的最长公共前缀,对于每一个模式串的前缀都要进行尝试匹配,而且匹配的时候,是要与所有的模式串匹配而不只是单个。所以说,我们先用Trie字典树存储所有的模式串,然后一次扫过所有的模式串。之后每一个点设置一个fail指针,表示失配之后应该匹配前缀相同的另一个模式串的位置。然后类似KMP算法,从头开始匹配,匹配成功则继续,否则滑动到fail的位置继续尝试,直到滑动回了根节点。时间复杂度为O(len),len表示所有串的长度。
首先是构建Trie和fail指针。构建Trie和普通的Trie区别不大,关键是fail指针,这个具体的我就盗别人一张图了,希望不要介意。
这里有abcd、abd、cd和bcd四个模式串,那么我的fail指针就按照图中那么设置,图中只画了c的,可以看到,当串的后缀与fail所指向的串的前缀有公共部分,且这两个一定是最长的。如果是在找不到公共部分,那么就fail指向根节点。具体实现的话,我们通常使用广搜迭代的方法。具体代码如下(以26个小写字母为例): void ins(char* x)
{
int o=root;
for(int k=0;k<strlen(x);k++)
{
int c=x[k]-'a';
if(!T[o].ch[c]) T[o].ch[c]=++tot;
o=T[o].ch[c];
}
T[o].cnt++;
}
void get_fail()
{
queue<int> q;
q.push(root);
while(!q.empty())
{
int o=q.front();
for(int i=0;i<26;i++)
{
if (!T[o].ch[i]) continue;
if (o!=root)
{
int fa=T[o].fail;
while(fa&&!T[fa].ch[i]) fa=T[fa].fail; //迭代fa,使得前缀相等且最后要有当前字母
T[T[o].ch[i]].fail=T[fa].ch[i]; //确定fail指针
} else T[T[o].ch[i]].fail=root;
q.push(T[o].ch[i]);
} q.pop();
}
}
然后就是匹配了。匹配正如之前说的,如果能配上,那么直接往后走,否则就往fail指针走,一直走到root为止。唯一需要注意的是每次匹配上之后,都要走一次fail指针,把所有相互包含的模式串也要计算进去。具体见代码:
int AC_automation(char *x) { int o=root,res=0; for(int i=0;i<strlen(x);i++) { int c=x[i]-'a'; while(o&&!T[o].ch[c]) o=T[o].fail; int j=o=T[o].ch[c]; while(j) { res+=T[j].cnt; j=T[j].fail; } } return res; }
大体来说模板部分就是这样的。一个插入、一个求fail指针和匹配。
下面就来说说模板题。 HDU 2222。大致题意就是,先是给出很多个模式串,最后再给出一个字段,然后问这些模式串中有多少个模式串出现在字段中,就是一个AC自动机的裸题。只有一个地方需要改进,那就是每个模式串发现被匹配之后不需要第二次计算,所以直接标记一下即可。具体见代码:
#include<bits/stdc++.h> #define INF 0x3f3f3f3f #define LL long long #define N 100010 using namespace std; struct Trie { struct node{int fail,cnt,ch[26];}T[250000]; int tot,root; void init() { tot=root=0; memset(T,0,sizeof(T)); } void ins(char* x) { int o=root; for(int k=0;k<strlen(x);k++) { int c=x[k]-'a'; if(!T[o].ch[c]) T[o].ch[c]=++tot; o=T[o].ch[c]; } T[o].cnt++; } void get_fail() { queue<int> q; q.push(root); while(!q.empty()) { int o=q.front(); for(int i=0;i<26;i++) { if (!T[o].ch[i]) continue; if (o!=root) { int fa=T[o].fail; while(fa&&!T[fa].ch[i]) fa=T[fa].fail; T[T[o].ch[i]].fail=T[fa].ch[i]; } else T[T[o].ch[i]].fail=root; q.push(T[o].ch[i]); } q.pop(); } } int AC_automation(char *x) { int o=root,res=0; for(int i=0;i<strlen(x);i++) { int c=x[i]-'a'; while(o&&!T[o].ch[c]) o=T[o].fail; int j=o=T[o].ch[c]; while(j) { res+=T[j].cnt; if (T[j].cnt==-1) break; //如果被标记,说明已经统计过,不需要重复 T[j].cnt=-1; j=T[j].fail; //做标记 } } return res; } } Trie; char s[1000010]; int n; int main() { int T_T; cin>>T_T; while(T_T--) { Trie.init(); scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%s",s); Trie.ins(s); } scanf("%s",s); Trie.get_fail(); printf("%d\n",Trie.AC_automation(s)); } return 0; }
相关文章推荐
- HDU 2222 Keywords Search 【AC自动机模板】
- hdu 2222 ac自动机更新模板 for onSite contest
- hdu 2222 ac自动机 模板
- [AC自动机模板题] HDU 2222 Keywords Search
- AC自动机(Aho-Corasick automation)模板 HDU:2222
- AC自动机入门+模板 (HDU 2222)
- HDU 2222 (AC自动机模板)
- hdu -2222 Keywords Search(AC自动机模板)
- HDU-2222 Keywords Search (AC自动机模板)
- hdu 2222 ac自动机模板
- HDU 2222 AC自动机模板题
- HDU 2222 Keywords Search (初学AC自动机)
- hdu 2222 ac自动机模板题
- hdu 2222(AC自动机模板)
- hdu 2222 AC自动机模板(非指针)
- Match:Keywords Search(AC自动机模板)(HDU 2222)
- hdu 2222 AC自动机模板题(指针版+数组版)
- HDU 2222 ac自动机模板题
- AC自动机 ( 动态建树模板 )——Keywords Search ( HDU 2222 )
- HDU 2222 AC自动机 模板