AC自动机=trie+kmp???
它的用处是判断一个字符串中是否含有多个字符串(即把kmp中的短串改为多个短串)。
最暴力的方法就是直接在trie树上匹配,但是这很慢,于是引入kmp的fail指针,在失配的时候直接转向指针指向的地方,不一定要从头开始重新匹配,从而减少匹配的时间。
定义:
struct type { ll next[10],tag,fail; //next[i]表示从trie树的一个节点经过字符为i的边连向的节点 //tag表示到此节点后是否有字符串在此结束//fail表示此节点的失配指针 }trie[2000];
插入:
和trie树的插入没有任何的区别,没有什么好说的
void ins(char *s) { ll i,x,now=0; for(i=0;i<strlen(s);i++) { x=s[i]-'0'; if(trie[now].next[x])now=trie[now].next[x];else now=trie[now].next[x]=++cnt;//走向下一个节点,如果没有就创建一个节点 } trie[now].tag=1;//字符串到now就结束了,打上结束标记 }
获得fail指针:
trie[i].fail的定义:若有一个节点j,使得从根节点到i节点的字符串的除了本身的所有后缀与从根节点到j节点的除了本身的所有前缀的相同子串最长,则j就是trie[i].fail。
可知trie[i].fail在树中的深度一定比i的深度小(就像kmp中字符的fail一定指向它前面的字符),于是我们可以用BFS来求出fail
首先,根节点的fail指向自己。
接下来,所有跟根节点有连边的节点的fail指向根节点(就像kmp的fail[1]等于0),并将它们进入队列。
然后开始BFS。对于当前队首的节点u,假设p是trie[u].fail。根据fail的定义,可知如果trie[p].tag!=0,那么从根节点到p节点的串就是从根节点到u节点的一个后缀,为了节省时间,我们将trie[u].tag+=trie[p].tag,因为p的深度一定比u的深度小,所以我们一定已经求出了p的fail,于是不需要继续遍历就可以得到在从根节点到u节点中在AC自动机里的所有子串的个数。为了求得所有与u连边的节点v的fail指针,很容易想到可以利用已知的p,所以trie[v].fail=trie[p].next[i]。然而可能没有trie[p].next[i]这个值,于是只能看有没有trie[trie[p].fail].next[i]这个值,如果还没有,就看有没有trie[trie[trie[p].fail].fail].next[i]这个值,如此直到有一个fail指针有一条边连向。为了节省时间,我们可以用trie图优化,即对于点u,如果它没有一种字符的边,就假设u通过这条边连向v,那么v=trie[p].next[i],之后就不用再通过while循环来查找了。
while(head<tail) { u=f[++head]; p=trie[u].fail; for(i=0;i<26;i++)//枚举每一条边(假设只有‘a’~‘z’这26个字符) { v=trie[u].next[i]; trie[u].tag+=trie[p].tag; if(v) { f[++tail]=v; trie[v].fail=trie[p].next[i]; }else trie[u].next[i]=trie[p].next[i];//建立trie图优化 } }
之后就可以利用tag或者fail做例如数位DP的事情了。
浙公网安备 33010602011771号