1. 程式人生 > 其它 >10. 最大公約數&最小公倍數&加法原理&乘法原理&排列組合

10. 最大公約數&最小公倍數&加法原理&乘法原理&排列組合

AC 自動機是更高維的 kmp。構造方法感覺更像是運用了 dp 的思想,先對所有模式串建立一棵 Trie 樹,然後考慮某個節點 \(x\),有個 \(w\) 的字尾,考慮如何去求它的 fail 值。於是就有了 AC 自動機的核心程式碼:

int x=q.front(),ff=t[x].fail;q.pop();
for(int i=0;i<26;i++)
	if(t[x].nxt[i])t[t[x].nxt[i]].fail=t[ff].nxt[i],q.push(t[x].nxt[i]);
	else t[x].nxt[i]=t[ff].nxt[i];

單純匹配的題有 第一塊板子

比較無腦的就是可以在 AC 自動機上 DP,和在 kmp 自動機上 DP 是同一個道理。

P4052 是板子,套路是以當前匹配到的位置 \(x\) 作為狀態進行轉移,決策就是下一個字元放啥,然後正常轉移即可,在這道題中的限制就是不能轉移到有 end 的節點,把這些點忽略即可。顯然它是可以用矩陣來描畫的,P3041 是矩陣快速冪優化 DP 套 AC 自動機,縫合題。P7456 不能算是 AC 自動機上 DP,而只能算是幫助 DP,不知道應該歸到哪裡。

一般的題目都會用到 fail 樹的性質。自動機上每個點往 fail 對應的點上連邊,顯然最後會形成一棵樹(每個點只會往更淺的點連線)。有一個顯然的性質是,對於一個節點 \(x\),它到根的路徑可以看成是 kmp 的一個匹配過程,所以實際上這些點的串都是 \(x\)

串的字尾,這就非常有用了。在 第二塊板子 中,我們現在停留在 \(x\),那麼實際上達到的效果是點到根所有點對應的串都出現了一次,然而暴力去找顯然是不行的。於是可以找每個點出現的次數,然後回答的時候可以看成是一次子樹求和(因為子樹內的值會對它產生貢獻)。P3966 也是一個意思。

fail 樹也是樹,所以可以樹剖,這牽扯到一個性質。首先根據字典樹的原理,一個節點在字典樹上到根的所有節點形成了它的字首集合,而一個串在另一箇中出現可以看成前者是後者字首的字尾。而前面也說了,\(A\)\(B\) 字尾表現為在 fail 樹上 \(B\)\(A\) 子樹中的節點,所以要統計一個串在另一個串中出現了幾次,就只需要把後者到根(字典樹上)的所有點標記一下,然後查詢前者子樹中有多少個標記節點即可,用樹狀陣列可以維護。

P2414 就是這個方法,一個結論是說對於題目中所言的過程,雖然串的總長度可能很大,但建出來的 Trie 樹不會太大(甚至可以邊輸入邊建樹)。把所有 \(y\) 離線下來排序之後按順序回答就可以保證修改的次數是線性的,這樣一來就可以用上面那種方法了,複雜度有一隻 log。CF163E 會更簡單一些,當前匹配到的點的貢獻是 fail 樹上點到根的權值之和,所以說修改操作就是單點修改,詢問就是鏈查詢,轉化成子樹修改單點查詢即可,樹狀陣列維護,一隻 log。