题意
给定一个字符串集,询问某个字符串包含哪些字符串集中的字符串。
链接:link。
思路
多模式匹配问题可以用AC自动机解决,AC自动机算法如下:
- 首先建立一颗字典树。
- 再利用BFS建立fail指针(指向那个最长后缀的浅层节点),建立fail指针的方式如下:节点u的第i个孩子的fail指针,应该指向节点fail[u]的第i个孩子节点,如果为空,则继续迭代地寻找fail指针,直到fail指针为空,或找到符合条件的节点,如果最终fail指针为空,则令当前的fail为root。
- 匹配时,先按照next往下匹配,如果next指针为空,则按照fail指针向上迭代,直到fail指针为空,或者next不为空,如果最终fail指针为空,则当前节点跳到根节点,继续下一次匹配,否则,按fail指针查找所有的后缀是否存在字符集中。
时间复杂度分析
建树的时间复杂度为
O
(
∑
i
=
1
n
L
i
)
\mathcal{O}(\sum_{i=1}^{n} L_{i})
O(∑i=1nLi),其中
L
i
L_{i}
Li表示字符集中第
i
i
i个字符串的长度。
查找的时间复杂度为
O
(
L
)
\mathcal{O}(L)
O(L),
L
L
L表示匹配串的长度(因为fail指针只会往浅层节点走,所以时间复杂度最多为
O
(
2
×
L
)
\mathcal{O}(2 \times L)
O(2×L))。
实现
#include<cstdio>
#include<queue>
using namespace std;
const int MAXN=1e6+5;
struct Node{
int cnt;
Node *fail,*next[26];
Node(){
cnt=0;
fail=nullptr;
for(int i=0;i<26;i++) next[i]=nullptr;
};
};
char s[MAXN];
void build_trie(Node *root,const char *keyword){
Node *p=root;
for(int i=0;keyword[i];i++){
int v=keyword[i]-'a';
if(p->next[v]==nullptr) p->next[v]=new Node;
p=p->next[v];
}
p->cnt++;
}
void build_AC_automation(Node *root){
queue<Node*> que;
que.push(root);
while(!que.empty()){
Node *cur=que.front();que.pop();
for(int i=0;i<26;i++){
if(cur->next[i]!=nullptr){
Node *p=cur->fail;
while(p!=nullptr){
if(p->next[i]!=nullptr){
cur->next[i]->fail=p->next[i];
break;
}
p=p->fail;
}
if(p==nullptr) cur->next[i]->fail=root;
que.push(cur->next[i]);
}
}
}
}
int match(Node *root){
int cnt=0;
Node *p=root;
for(int i=0;s[i];i++){
int v=s[i]-'a';
while(p!=nullptr&&p->next[v]==nullptr) p=p->fail;
if(p==nullptr){
p=root;
continue;
}
p=p->next[v];
Node *tmp=p;
while(tmp!=root){
if(tmp->cnt){
cnt+=tmp->cnt;
tmp->cnt=0;
}
else break;
tmp=tmp->fail;
}
}
return cnt;
}
void delete_trie(Node *root){
queue<Node*> que;
que.push(root);
while(!que.empty()){
Node *cur=que.front();
que.pop();
for(int i=0;i<26;i++){
if(cur->next[i]!=nullptr){
que.push(cur->next[i]);
}
}
delete cur;
}
}
int main(){
int T;scanf("%d",&T);
while(T--){
Node *root=new Node;
int n;scanf("%d",&n);
for(int i=0;i<n;i++){
char keyword[55];
scanf("\n%s",keyword);
build_trie(root,keyword);
}
build_AC_automation(root);
scanf("\n%s",s);
printf("%d\n",match(root));
delete_trie(root);
}
return 0;
}