第一次写AC自动机,参考集训手册题解的代码,但是还是花了三个小时的时间才A掉。字符串
插入字典树的过程就是一般字典树形成的过程。这里关键在于求失败指针。有点像KMP求next的过程。
对于每个结点,我们可以这样处理:设这个结点上的字母为C,沿着他父亲的失败指针走,直到
走到一个结点,他的儿子中也有字母为C的节点。然后把当前结点的失败指针指向那个字母也为C的结
点。如果一直走到了root都没找到,那就把失败指针指向root。最开始,我们把root加入队列(root
的失败指针显然指向自己),这以后我们每处理一个结点,就将他的所有儿子几点入队。
假设有一个节点k,他的失败指针指向j。那么k,j满足这个性质:设root到j的距离为n,则从k之上
的第n - 1个节点到k所组成的长度为n的单词,与从root之后第一个结点到j所组成的单词相同。
/*Accepted 2222 187MS 26636K 2048 B G++ Yu*/ #include<stdio.h> #include<string.h> #include<stdlib.h> #include<queue> using namespace std; #define init(x) (memset(trie[x], 0, sizeof(trie[x]))) #define next 26 #define root 0 #define cnt 27 const int MAXN = 10001; const int MAXL = 1000001; const int MAXM = 28; int trie[MAXN * 50][MAXM], ntp; char src[MAXL]; queue<int> q; void inser(char *s) { int i, cur = root, t; for(i = 0; s[i]; i ++) { t = s[i] - 'a'; if(!trie[cur][t]) init(ntp),trie[cur][t] = ntp ++; cur = trie[cur][t]; } ++ trie[cur][cnt]; } void GetNext() //广搜的过程 { int cur, t, i; q.push(root); trie[root][next] = root; while(!q.empty()) { cur = q.front(), q.pop(); for(i = 0; i < 26; i ++) { if(trie[cur][i]) { if(cur == root) //如果父亲结点是根,那么失败就只能返回根 trie[trie[cur][i]][next] = root; else { t = trie[cur][next]; //trie[cur][i]的父亲cur的失败指针 while(t && !trie[t][i]) //找到一个和trie[cur][i]字符相同的结点所处的位置 t = trie[t][next]; trie[trie[cur][i]][next] = trie[t][i]; //将trie[cur][i]的失败指针指向相同的点 } q.push(trie[cur][i]); //将cur的所有儿子结点入队 } else { trie[cur][i] = trie[trie[cur][next]][i]; //不存在这一点,指向父亲结点失败指针的第i个儿子 } } } } int cal(char *s) { int i, j, cur = root, t, ans = 0; for(i = 0; s[i]; i ++) { t = s[i] - 'a'; cur = trie[cur][t]; j = cur; while(j && trie[j][cnt] != -1) { ans += trie[j][cnt]; trie[j][cnt] = -1; j = trie[j][next]; } } return ans; } int main() { int T, n; scanf("%d", &T); while(T --) { scanf("%d", &n); ntp = 1; init(root); while(n --) { scanf("%s", src); inser(src); } GetNext(); scanf("%s", src); printf("%d\n", cal(src)); } return 0; }