您的位置:首页 > 其它

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指针,把所有相互包含的模式串也要计算进去。具体见代码:
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: