输入
每个输入文件有且仅有一组测试数据。
每个测试数据的第一行为一个整数N,表示河蟹词典的大小。
接下来的N行,每一行为一个由小写英文字母组成的河蟹词语。
接下来的一行,为一篇长度不超过M,由小写英文字母组成的文章。
对于60%的数据,所有河蟹词语的长度总和小于10, M<=10
对于80%的数据,所有河蟹词语的长度总和小于10^3, M<=10^3
对于100%的数据,所有河蟹词语的长度总和小于10^6, M<=10^6, N<=1000
输出
对于每组测试数据,输出一行"YES"或者"NO",表示文章中是否含有河蟹词语。
6 aaabc aaac abcc ac bcd cd aaaaaaaaaaabaaadaaac样例输出
YES
题目链接:https://hihocoder.com/problemset/problem/1036
题目分析:裸的AC自动机,其思想是在Trie树的基础上增加失败转移,利用当前失配的后缀是某条路径的前缀性质,这很像KMP,可以省去很多重复的比较
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int const MAX = 1e6 + 5;
char s[MAX], t[MAX];
struct Trie {
Trie *nxt[26];
Trie *fail;
int cnt;
Trie() {
memset(nxt, 0, sizeof(nxt));
fail = NULL;
cnt = 0;
}
};
void Insert(Trie *root, char *s) {
int len = strlen(s);
Trie *p = root;
for (int i = 0; i < len; i++) {
int idx = s[i] - 'a';
if (p -> nxt[idx] == NULL) {
p -> nxt[idx] = new Trie();
}
p = p -> nxt[idx];
}
p -> cnt ++;
}
void Build_AC(Trie *root) {
queue <Trie*> q;
q.push(root);
while (!q.empty()) {
Trie *cur = q.front();
q.pop();
for (int i = 0; i < 26; i++) {
if (cur -> nxt[i] == NULL) {
if (cur == root) {
cur -> nxt[i] = root;
} else {
cur -> nxt[i] = cur -> fail -> nxt[i];
}
} else {
if (cur == root) {
cur -> nxt[i] -> fail = root;
} else {
cur -> nxt[i] -> fail = cur -> fail -> nxt[i];
}
q.push(cur -> nxt[i]);
}
}
}
}
bool Query(Trie *root, char *t) {
int len = strlen(t);
Trie *p = root;
for (int i = 0; i < len; i++) {
int idx = t[i] - 'a';
while (p -> nxt[idx] == NULL && p != root) {
p = p -> fail;
}
p = p -> nxt[idx];
if (p == NULL) {
p = root;
}
Trie *tmp = p;
while (tmp != root && tmp -> cnt != -1) {
if (tmp -> cnt > 0) {
return true;
}
tmp -> cnt = -1;
tmp = tmp -> fail;
}
}
return false;
}
int main() {
int n;
scanf("%d", &n);
Trie *root = new Trie();
while (n--) {
scanf("%s", s);
Insert(root, s);
}
scanf("%s", t);
Build_AC(root);
printf("%s\n", Query(root, t) ? "YES" : "NO");
}