NOIP大纲整理:(六)字符串3:AC自动机

3、AC自动机

有n个模式串,长度之和是|T|,有一个主串,长度是|S|,问哪些模式串是这个主串的子串(或者有多少个模式串在主串中出现过)? 

解法一:直接跑n次KMP算法,时间复杂度:O(n×|S|)。 

解法二:AC自动机,时间复杂度:O(|T|+|S|),对于n个串,构建trie树,在trie树上做KMP。 

在这里我来详解一下AC自动机啊~ 

首先我们定义一个指针,叫做“失配指针”或者“失败指针”,在KMP算法中,这个失配指针就是next[]数组,同样地在AC自动机中,在trie树上也定义一个失配指针与此类似但不完全相同。 

失配指针:假设一个节点k的失配指针指向j,那么k、j满足性质:设根节点root到j的距离为n,则从k以上的第n个节点到k这个节点所组成的长度为n的单词,与根节点root到j所组成的单词完全相同。 

如下图:

单词"she"中的'e'的失配指针指向的是单词"her"中的'e',因为红框中的部分是完全一样的。 

然后,问题来了,我们该怎样处理这个失配指针呢?其实我们可以用BFS就很方便地解决了。 

处理过程:让和根节点直接相连的节点的失配指针指向根节点,对于其他节点(假设为a),设这个节点上的字母为ch,沿着a的父亲b的失配指针走,一直走到一个节点c,c的儿子中也有字母为ch的节点d,然后把a节点的失配指针指向c节点的儿子d(因为d的字母也为ch),如果一直走到了根节点都没找到,那就把失配指针指向根节点。 

最开始,我们把根节点加入队列(根节点的失败指针显然指向自己),这以后我们每处理一个点,就把它的所有儿子加入队列,直到搞完。 

这样我们就得到了一棵带有失配指针的trie树了,接下来正式介绍AC自动机工作原理! 

AC自动机原理:对于一棵trie树,我们用黄色表示一个单词(某个模式串)的末尾,也就是说从根节点走到一个黄色的点,就组成一个“单词”,如下图:

一开始,trie树中有一个指针t1指向根节点root,将这n个模式串合并为一个模式主串,模式主串中有一个指针t2指向这个模式主串的串头。 

接下来进行类似KMP算法的操作:如果t2指向的字母,是trie树中,t1指向的节点的儿子,那么把t2+1,t1改为那个儿子的编号,否则t1顺这当前节点的失配指针往上找,直到t2是t1的一个儿子,或者t1指向根为止。 

如果t1经过了一个黄色的点,那么以这个点结尾的单词就算出现过了(这个模式串已经在主串中出现了),或者如果t1所在的点可以沿着失配指针走到一个黄色的点,那么以那个黄色的点为结尾的单词就算出现过了(这个模式串已经在主串中出现了),记录答案即可。

  

经典例题:假装是字符串的题——正则表达式 

给定一个字符串,判断其是否为合法的正则表达式。 

一个正则表达式定义为: 

①0是正则表达式,1也是正则表达式。 

②P和Q都是正则表达式,则PQ也是正则表达式。 

③P是正则表达式,则(P)是正则表达式 

④P是正则表达式,则P*也是正则表达式 

⑤P和Q都是正则表达式,则P|Q是正则表达式 

举个栗子: 

010101101*

(11|0*)* 

以上都是都是正则表达式

|S|<=100 

解题思路:令dp[i][j]表示第i个字符到第j个字符能否组成正则表达式,分5种情况进行转移就可以了。 

暂时代码是转载的,以后有机会会更新,看不懂请跳过

例:UVA 11468
#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<string>  
#include<algorithm>  
#include<map>  
#include<queue>  
#include<set>  
#include<stack>  
#include<cmath>  
#include<vector>  
#define inf 0x3f3f3f3f  
#define Inf 0x3FFFFFFFFFFFFFFFLL  
#define eps 1e-9  
#define pi acos(-1.0)  
using namespace std;  
typedef long long ll;  
const int maxn=2000+10;  
double p[110],dp[maxn][110];  
bool vis[maxn][110];  
int ch[maxn][63],val[maxn],next[maxn],size,n;  
int idx(char c)  
{  
    if(c>='0'&&c<='9') return c-'0';  
    if(c>='a'&&c<='z') return c-'a'+10;  
    return c-'A'+10+26;  
}  
void Init()  
{  
    memset(vis,0,sizeof(vis));  
    memset(ch[0],0,sizeof(ch[0]));  
    memset(next,0,sizeof(next));  
    memset(val,0,sizeof(val));  
    memset(p,0,sizeof(p));  
    size=0;  
}  
void insert(const char *s)  
{  
    int u=0,len=strlen(s);  
    for(int i=0;i<len;++i)  
    {  
        int c=idx(s[i]);  
        if(!ch[u][c])  
        {  
            ch[u][c]=++size;  
            memset(ch[size],0,sizeof(ch[size]));  
            val[size]=0;  
        }  
        u=ch[u][c];  
    }  
    val[u]=1;  
}  
void build()  
{  
    queue<int>q;  
    for(int i=0;i<62;++i)  
    {  
        if(ch[0][i]) q.push(ch[0][i]);  
    }  
    while(!q.empty())  
    {  
        int u=q.front();q.pop();  
        for(int i=0;i<62;++i)  
        {  
            int v=ch[u][i];  
            if(!v) {ch[u][i]=ch[next[u]][i];continue;}  
            q.push(v);  
            int j=next[u];  
            while(j&&!ch[j][i]) j=next[j];  
            next[v]=ch[j][i];  
            val[v]|=val[next[v]];  
        }  
    }  
}  
double f(int u,int L)  
{  
    if(L==0) return 1.0;  
    if(vis[u][L]) return dp[u][L];  
    vis[u][L]=true;  
    dp[u][L]=0;  
    for(int i=0;i<62;++i)  
    {  
        if(!val[ch[u][i]])  
            dp[u][L]+=p[i]*f(ch[u][i],L-1);  
    }  
    return dp[u][L];  
}  
char str[110];  
int main()  
{  
    //freopen("in.txt","r",stdin);  
    //freopen("out.txt","w",stdout);  
    int t,tcase=0;  
    scanf("%d",&t);  
    while(t--)  
    {  
        tcase++;  
        Init();  
        int K;  
        scanf("%d",&K);  
        for(int i=0;i<K;++i)  
        {  
            scanf("%s",str);  
            insert(str);  
        }  
        scanf("%d",&n);  
        char c[3];  
        for(int i=0;i<n;++i)  
        {  
            scanf("%s",c);  
            scanf("%lf",&p[idx(c[0])]);  
        }  
        build();  
        int L;  
        scanf("%d",&L);  
        double ans=f(0,L);  
        printf("Case #%d: %lf\n",tcase,ans);  
    }  
    return 0;  
}  

---------------------------------

猜你喜欢

转载自blog.csdn.net/liusu201601/article/details/81435707
今日推荐