做了一天了,看懂了AC自动机的原理,纯属脑洞码了一天~
几个小函数解释如下:
- insert即trie树建立的过程
- KMP是找到trie树中的每个节点的后缀节点,和一维的字符串差不多
- find(x,d)是找到x结点下经过字符d转化到的下一个结点,可能会出现没有的情况,标记为0,即重新返回根节点
HDU2222
注意点:
- 若关键字A与关键字B重复,在主串中出现一个算两次
- 若关键字A在主串种出现两次,则只算一次
- 在主串经过的每个结点,都访问该节点的后缀节点,看其是否是终止结点。
- 访问过的一系列后缀节点可以做标记,下次访问无需再重新累加。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
int ans, size;
struct node
{
char ch;
int next[26], fa, end, pre, have;
void pr(){
printf("%c %d %d %d\n", ch, fa, pre, end);
}
}tree[250005];
char str[10001][51];
void insert(char *key)
{
char *p = key;
int s = 0;
while(*p)
{
if(!tree[s].next[*p-'a'])
tree[s].next[*p-'a'] = ++size;
int t = s;
s = tree[s].next[*p-'a'];
tree[s].fa = t;
tree[s].ch = *p;
p++;
}
tree[s].end++;//repeated key words can be calculated more than once
}
int find(int x, char d)
{
return tree[x].next[d-'a'];
}
void KMP(char *key)
{
char *p = key;
int s = find(0, *p);
tree[s].pre = 0;
p++;
while(*p)
{
int i = s;
s = find(s, *p);
//printf("%d %c\n", s, *p);
int j = tree[i].pre;
while(j > 0 && !find(j, *p))
j = tree[j].pre;
tree[s].pre = find(j, *p);
p++;
}
}
int sig(int x)//visit x, x.pre, x.pre.pre, ....
{
//printf("sig tree[%d] have %d pre %d end %d\n",
//x, tree[x].have, tree[x].pre, tree[x].end);
if(!x)
return 0;
if(tree[x].have)
return 0;
tree[x].have = 1;
return tree[x].end + sig(tree[x].pre);
}
int main()
{
int T, n;
scanf("%d", &T);
while(T--)
{
memset(tree, 0, sizeof(tree));
ans = size = 0;
scanf("%d", &n);
for(int i = 0;i < n;i++)
{
scanf("%s", str[i]);
insert(str[i]);
}
tree[0].pre = 0;
for(int i = 0;i < n;i++)
KMP(str[i]);
/*for(int i = 0;i <= size;i++)
{
printf("tree[%d] ", i);
tree[i].pr();
}*/
char c;
int s = 0;
getchar();
while((c = getchar()) != '\n')
{
//printf("this %c ", c);
while(s > 0 && !tree[s].next[c-'a'])
{
s = tree[s].pre;
ans += sig(s);
}
if(tree[s].next[c-'a'])
{
ans += sig(s);
s = tree[s].next[c-'a'];
}
ans += sig(s);
//printf("s %d ans %d\n", s, ans);
}
printf("%d\n", ans);
}
return 0;
}