Trie圖和AC自動機的區別
Trie圖是AC自動機的確定化形式,即把每個結點不存在字符的next指針都補全了。這樣做的好處是使得構造fail指針時不需要next指針為空而需要不斷回溯。
比如構造next[cur][i]的fail指針,cur為父節點,next[cur][i]為cur的兒子結點,如果是AC自動機,如果父親結點tmp(tmp是cur的一份拷貝)的next[fail[tmp]][i]不存在時,需要讓tmp不斷回溯(即tmp = fail[tmp]),直到next[fail[tmp]][i]不為空時,才讓fail[next[cur][i]] = next[fail[tmp]][i]。
如果是Trie圖,那么直接讓fail[next[cur][i]] = next[fail[cur]][i]就可以了,因為Trie圖已經補全了next指針。
但是不管是Trie圖還是AC自動機,它們的fail指針的指向都是一模一樣的。所以不管是用Trie圖還是AC自動機都可以構造fail樹。不過Trie圖比AC自動機好寫多了,所以我一直都是寫Trie圖而不是自動機。
fail指針的性質
要能夠靈活使用Fail樹,首先需要了解fail指針的性質,所以先說下fail指針都有哪些性質。
每個結點的fail指針都指向自己的最長后綴,那么很重要的一個性質就是讓一個結點cur的fail指針不斷回溯向上走,直到碰到根結點為止,那么回溯時經過的結點所代表的字符串都是結點cur所代表的字符串的后綴。
什么是Fail樹
下面的第一幅圖是AC自動機,第二幅圖是Fail樹。之所以第一幅圖是AC自動機而不是Trie圖的原因是Trie圖太特么難畫了。不過具體的原理還是沒有變的。
可以看出Fail樹其實就是將AC自動機的next指針去掉,然后反轉fail指針的指向所構造出來了,而且可以肯定這一定是一棵樹 ,所以稱之為Fail樹。
Fail樹的一個性質是,某個結點所對應的字符串肯定是其兒子結點,孫子結點. . .所對應的字符串的后綴。
Fail樹的應用
如果有n個字符串,所有字符串的長度加起來不超過$10^6$,有m個查詢,要查詢第x個字符串在第y個字符串中出現了多少次。
如果是使用AC自動機查詢,可以直接對字符串構建AC自動機,然后讓y去走AC自動機,對於走過的結點,把其權值加1。那么要查詢x在y中出現了多少次,便要從底層開始,順着fail指針把權值上傳。然后只要查詢x結點的權值是多少就知道x在y中出現了多少次。每次查詢的復雜度是O(tot+len[y]),其中tot是AC自動機的結點總數。
如果是使用Fail樹進行查詢,那么只要查詢所有子結點的權值和就好了,子結點的權值和可以使用dfs序和樹狀數組來維護。然后同樣讓有去走AC自動機,將走過的結點的權值加1,只不過現在是用樹狀數組來維護權值。那么要查詢x在y中出現了多少次,只要進行一次區間查詢就可以了,即只要查詢x結點的所有子結點就好了(根據fail樹的性質),因為其dfs序號是連續的,所以是一次區間查詢。可以將查詢按照y排序,然后對具有相同y的查詢一起查詢。每次查詢時間復雜度是O(len[y]+log(tot))。
該文章在我的個人博客地址是:http://www.alphaway.org/post-440.html