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;
}
---------------------------------