int trie[N][26],tot=1; //设字符串由小写字母构成 bool end[N]; //插入一个字符串 voidinsert(char* s){ int l=strlen(s),p=1; for(int k=0;k<l;++k){ int ch=s[k]-'a'; if(trie[p][ch]==0) trie[p][ch]=++tot; p=trie[p][ch]; } end[p]=true; } // 搜索一个字符串 boolsearch(char* s){ int l=strlen(s),p=1; for(int k=0;k<l;++k){ p=trie[p][s[k]-'a']; if(p==0) returnfalse; } return end[p]; }
AC自动机
大家都知道KMP算法是求单字符串对单字符串的匹配使用的,那么,多个字符串在一个字符串上的匹配怎么办? 如: 求 abab 在 abababbabbaabbabbab 中出现了几次。 很明显,求出abab的 $ne$ 数组,然后进行KMP的匹配即可出解。 辣么——求 aba 、 aca 、 bab 、 sab 、 sba 在字符串 asabbasbaabbabbacaacbscbs 中总共出现了几次。此时如果再对于每一个需要匹配的串求一次 $ne$ 数组,时间复杂度过大了…… 辣么,AC自动机是如何解决此问题的呢?
开始前,先看看定义:
1 2 3 4 5
structTrie{//字典树 int fail; //失配指针 int vis[26]; //子节点位置 int end; //标记结尾 }ac[N];
然后开始吧! 首先,将所有的需要匹配的字符串构造成一棵Tire树,如图(灰色节点表示一个单词的结尾): 然后,类似于KMP建立失配指针(即 $ne$ 数组),其定义为:当前节点所代表的串中,最长的、能与后缀匹配的,在 Trie 中出现过的前缀所代表的节点。 类似的,可以通过跳转来省略重复的检查。 如图是已建立好的: 每次沿着Trie树匹配,匹配到当前位置没有匹配上时,直接跳转到失配指针所指向的位置继续进行匹配。 所以,这个Trie树的失配指针要怎么求? 我们发现Trie树的失配指针是指向:沿着其父节点的失配指针,一直向上,直到找到拥有当前这个字母为子节点的节点,它的那个子节点就是目标。 如上图中 she 中的 e ,它的父节点是 h ( s 下面那个),而 h 的失配指针指向另一个 h (根下面那个),且这个 h 的子节点中有一个 e ,就将 she 中的 e 指向这个 e 。 需要注意的是,若找不到符合要求的节点,就将失配指针指向根,不难发现,第二层的所有节点的失配指针,都要直接指向Trie树的根节点。 代码:
//P5357 #include<bits/stdc++.h> usingnamespace std; constint N = 300005; structTrie { int fail; int vis[26]; int end; int as; } ac[N]; int cnt = 0; int re[N], in[N], ans[N]; string s[N]; queue<int> q; voidbuild_Trie(string s, int Num) { int l = s.length(); int now = 0; for (int i = 0; i < l; ++i) { if (!ac[now].vis[s[i] - 'a']) ac[now].vis[s[i] - 'a'] = ++cnt; now = ac[now].vis[s[i] - 'a']; } if (!ac[now].end) ac[now].end = Num; re[Num] = ac[now].end; return; } voidget_fail() {
for (int i = 0; i < 26; ++i) if (ac[0].vis[i] != 0) { ac[ac[0].vis[i]].fail = 0; q.push(ac[0].vis[i]); } while (!q.empty()) { int u = q.front(); q.pop(); for (int i = 0; i < 26; ++i) { int v = ac[u].vis[i]; if (v) { ac[v].fail = ac[ac[u].fail].vis[i]; in[ac[v].fail]++; q.push(v); } else ac[u].vis[i] = ac[ac[u].fail].vis[i]; } } return; } voidAC_query(string s) { int l = s.length(); int now = 0; for (int i = 0; i < l; ++i) { now = ac[now].vis[s[i] - 'a']; ac[now].as++; } return; } voidtopu() { for (int i = 1; i <= cnt; ++i) if (!in[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); ans[ac[u].end] = ac[u].as; int v = ac[u].fail; in[v]--; ac[v].as += ac[u].as; if (!in[v]) q.push(v); } return; } intmain() { int n; cin >> n; cnt = 0; for (int i = 1; i <= n; ++i) { cin >> s[i]; build_Trie(s[i], i); } ac[0].fail = 0; get_fail(); cin >> s[0]; AC_query(s[0]); topu(); for (int i = 1; i <= n; ++i) cout << ans[re[i]] << endl; return0; }