标签:
用于一个串的自我匹配或者与另一个串的匹配。
int j = -1;
next[0] = -1; //!!!!!!!!!
for(int i = 1; i < lena; i ++){ while(j >= 0 && a[j + 1] != a[i])j = next[j]; if(a[j + 1] == a[i])j ++; next[i] = j; } j = -1; for(int i = 0; i < lenb; i ++){ while(j >= 0 && a[j + 1] != b[i])j = next[j]; if(a[j + 1] == b[i])j ++; if(j == lena - 1){ ans ++; j = next[j]; }
第一个元素的next值是要特判的, 不然会有问题。
找最小循环节
一个有循环节 k 的串 它从第k + 1 位到第 n 位的next数组是单调的。
当然SA也可以O(n) 找最小循环节, 但代码量差了太多,,
一个串与多个串的匹配。
void insert(char *s){ int len = strlen(s), now = root; for(int i = 0; i < len; i ++){ if(next[now][s[i] - ‘a‘] == -1)next[now][s[i] - ‘a‘] = newnode(); now = next[now][s[i] - ‘a‘]; }end[now] ++; } void build(){ queue<int>q; fail[root] = root; //!!!!!!!!!! for(int i = 0; i < 26; i ++) if(next[root][i] == -1)next[root][i] = root; else{ fail[next[root][i]] = root; q.push(next[root][i]); } while( ! q.empty()){ int now = q.front(); q.pop(); for(int i = 0; i < 26; i ++) if(next[now][i] == -1)next[now][i] = next[fail[now]][i]; else{ fail[next[now][i]] = next[fail[now]][i]; q.push(next[now][i]); } } }
ac自动机把kmp的母串拓展到多个, 即计算一棵Trie树的fail数组
由于fail数组的单调性(p节点的fail一定指向一个深度小于p的点)所以在构造的时候 bfs 一遍就可以了。
一个优化是 如果next[p][c] 为空, 那么把它指向next[f][c] (f是p的所有next[x][c]不为空的祖先x中深度最大的节点)。
因为考虑寻找fail以及匹配的时候如果 next[p][c] 为空, 我们就会把它一直while上去直到一个非空的祖先,但是这一步在很多题中是可以直接在bfs的时候这样预处理出来的。 这样才能保证构建fail指针的复杂度是严格 O(字符串长度) 的。
当然在一些题中这种做法(由于空间限制或者题目需要)是行不通的,这时候只能用不改变next数组的复杂度没有保证的ac自动机, 或者用其它东西来搞啦。
注意 ac自动机的root 节点的 fail指针也是要特判的!!!
这个串走到的所有的节点,以及这些节点一路往根fail的节点,都是这个串的子串 (任意一个前缀的任意一个后缀)
1) end数组
end 数组的基本用法是存trie树中的这个点是否是一个字符串的结尾, 但考虑到如果当前节点是当前匹配到的串的后缀, 那么它的后缀还是当前匹配到的串的后缀, 所以在一些题中可以用end数组记录下fail[end], fail[fail[end]] …… root 的信息。 (用 + 或是 |)
2)fail树的构造
在一些题中(经常是要对子串进行修改的时候), 无法只用一个end就记录下fail[end], fail[fail[end]] …… root 的信息。 这时候考虑把所有fail的边反向, 于是就构成了一棵树。然后问题转变为在一棵子树上的一些操作, 这个时候就可以用树状数组线段树之类的来水了。
例, [Noi2011]阿狸的打字机, hdu4117 GRE words, cf 163E
基本的就是 一个(些)(……的)字符串在一个(些)(……的)字符串中出现了多少次
然后似乎就没有什么其它的应用了?
标签:
原文地址:http://www.cnblogs.com/lixintong911/p/4403575.html