动手实现--AC自动机
Trie树:
把若干个单词按前缀合并就得到一棵树,这棵树称为Trie树。Trie树是有根树,每条边表示一个字符,每个节点表示一个从根到当前节点的唯一路径上的字符依次连接得到的字符串。由于空串是任何串的前缀,因此根就表示“空串”这个串。如何区分单词节点和非单词节点呢?插入单词的时候对每个节点mark一下即可。 |
KMP算法思想:
能匹配就匹配,不能匹配就进行尽量小的平移来达到匹配。 |
有限自动机:
自动机是一个处理信息的机器,它的核心是状态和状态转移(和dp一样??),通过设计不同的状态和状态转移函数,来得到不同功能的自动机,因此自动机的应用非常广泛。 |
ac自动机
对字符串S构造一个这样的自动机:假设自动机扫描字符串T后处于状态w(w是一个整数,表示匹配长度),那么T的后w个字符是S的前缀,且w是满足这个性质的最大值。那么状态转移函数就可以这样定义:w + 字符c --> q,表示[Tc]的后q个字符是S的前缀,且这个q是满足这个性质的最大值。因此,状态转移矩阵很容易在O(m3Σ)的时间内求出来。 上述自动机慢在确定q需要花费O(m2)的时间,由kmp算法思想知道,如果S[w] == c,那么q = w + 1,否则w需要回退。那么我们利用kmp的回退数组(next数组),可以将复杂度降低到接近O(mΣ)。 同样,考虑多串的情形,则利用队列分层计算失配数组。这里会产生1个新的问题,假设当前匹配到了某个状态,这个状态表示的字符串为S,那么意味着不仅找到了S,而且找到了S的所有后缀,具体解决方法是给每个状态增加1个后缀链接,指向它的最大后缀单词,这样在找的时候要加速不少。 |
code(hdu2222,统计有多少模板串出现在了文本串里面):
#include <bits/stdc++.h> using namespace std; #define X first #define Y second #define pb(x) push_back(x) #define mp(x, y) make_pair(x, y) #define all(a) (a).begin(), (a).end() #define mset(a, x) memset(a, x, sizeof(a)) #define mcpy(a, b) memcpy(a, b, sizeof(b)) #define cas() int T, cas = 0; cin >> T; while (T --) template<typename T>bool umax(T&a, const T&b){return a<b?(a=b,true):false;} template<typename T>bool umin(T&a, const T&b){return b<a?(a=b,true):false;} typedef long long ll; typedef pair<int, int> pii; #ifndef ONLINE_JUDGE #include "local.h" #endif int ans; class ACAutomaton { public: void clear() { memset(node, 0, sizeof(node)); sz = 1; } void insert(char P[]) { int now = 0; for (int i = 0; P[i]; i ++) { int id = index(P[i]); if (!node[now][id]) node[now][id] = sz ++; now = node[now][id]; } node[now].cnt ++; node[now].final_state = true; } void build() { queue<int> Q; for (int i = 0; i < SZ; i ++) { if (node[0][i]) { Q.push(node[0][i]); } } while (!Q.empty()) { int ch = Q.front(); Q.pop(); for (int i = 0; i < SZ; i ++) { int next = node[ch][i]; if (next) { int now = node[ch].fail; while (now && !node[now][i]) now = node[now].fail; int buf = node[now][i]; node[next].last = node[next].fail = buf; if (!node[buf].final_state) node[next].last = node[buf].last; Q.push(next); } } } } void work(char T[]) { int now = 0; for (int i = 0; T[i]; i ++) { int id = index(T[i]); while (now && !node[now][id]) now = node[now].fail; now = node[now][id]; find(i, now); } } private: const static int N = 250007; const static int SZ = 26; struct Node { int next[SZ], fail, last; bool final_state; int cnt; int &operator[] (int p) { return next[p]; } }; Node node[N]; int sz; void find(int p, int now) { if (now == 0) return; if (node[now].final_state) process(p, now); find(p, node[now].last); } int index(char ch) { return ch - 'a'; } void process(int p, int now) { ans += node[now].cnt; node[now].final_state = false; } }; ACAutomaton ac; char s[100], t[1234567]; int main() { #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); //freopen("out.txt", "w", stdout); #endif // ONLINE_JUDGE int T, n; cin >> T; while (T --) { cin >> n; ac.clear(); for (int i = 0; i < n; i ++) { scanf("%s", s); ac.insert(s); } scanf("%s", t); ac.build(); ans = 0; ac.work(t); cout << ans << endl; } return 0; }