咱們比較瞭解的是有關多項式的乘法運算,對於下標爲整數,下標運算爲相加等於某個數的時候,咱們有很優秀的FFT作法。算法
可是遇到一些奇怪的卷積形式時,好比咱們定義 $h = f * g$, $h_{S} = \sum\limits_{L \subseteq S}^{} \sum\limits_{R \subseteq S}^{} [L \cup R = S] f_{L} * g_{R}$。spa
此時下標是一個集合,運算爲集合並的卷積,咱們已知了 $f$ 和 $g$ ,須要快速算出 $h$。code
最暴力的作法是 $O(2^{n})$ 分別枚舉 $L$ 和 $R$,把答案加到 $h$ 中去,這樣複雜度是 $O(4^{n})$,不太行。blog
這時咱們就要一種高效的算法。求卷積能夠用分治乘法,好像比較高妙,但咱們要講的是另外一種:快速莫比烏斯變換和反演。it
類比FFT,FMT也須要先把 $f$ 和 $g$ 求點值,點值相乘後再插值回去,快速莫比烏斯變換就至關於點值,快速莫比烏斯反演就至關於插值。class
具體證實:原理
因而問題就在於如何快速求出 $f$ 和 $g$ 莫比烏斯變換(反演)。循環
若是要暴力的話,能夠直接枚舉子集算出莫比烏斯變換(反演),這樣是 $O(3^{n})$,雖然比較優秀了,但複雜度還能更低。方法
咱們考慮用遞推解決:im
這樣咱們就能在 $O(n * 2^{n})$ 快速求出 $f$ 的莫比烏斯變換了。(逆莫比烏斯變換同理)
因而咱們就解決了集合並卷積的問題。
UPD:
咱們都知道第一層循環枚舉集合,第二層循環枚舉它爲$1$的位,把去掉這個$1$的子集的答案加上去的作法是錯的。咱們考慮兩個集合$s, t$,其中$t \in s$。$t$可能有多種路徑到達$s$,也就是存在多個$k, k \in s, t \in k$,這樣$t$就會被算屢次。
這裏有一個感性理解的方法,爲何第一層枚舉位第二層枚舉集合是對的,也就是每個集合它的全部子集的貢獻只被算了一次。
咱們假設$k_{1}$爲$t$並上第一個和$s$不同的位,咱們發現$t$的答案會先算到$k_{1}$上,而對於其餘的$k$,在$t$的答案算上來的時候本身的答案已經會先算上去了。
而對於逆莫比烏斯變換,若是理解了莫比烏斯變換後,其本質就是一個容斥。
void Fmt(int *a) { for (int i = 0; i < n; ++i) for (int s = 0; s < U; ++s) if (s >> i & 1) a[s] = Add(a[s], a[s ^ (1 << i)]); }
void Ifmt(int *a) { for (int i = 0; i < n; ++i) for (int s = 0; s < U; ++s) if (s >> i & 1) a[s] = Sub(a[s], a[s ^ (1 << i)]); }
(此處$n$爲集合大小, $U = 2^n$)
接下來咱們來繼續講一講子集卷積:
問題是已知 $f$ 和 $g$,咱們想求出 $h = f * g$,其中 $h_{S} = \sum\limits_{T \subseteq S} f_{T} * g_{S - T}$。
回顧剛剛的集合並卷積,子集卷積的條件比集合並卷積更苛刻,即 $L$ 和 $R$ 的集合應該不相交。
考慮集合並卷積合法當且僅當 $L \cap R = \varnothing$,咱們能夠在卷積時多加一維,維護集合的大小,如 $f_{i,S}$ 表示集合中有 $i$ 個元素,集合表示爲 $S$。能夠發現當 $i$ 和 $S$ 的真實元素個數符合時纔是對的。
初始時,咱們只把 $f_{bc[S],S}$ 的值賦成原來的 $f_{S}$($g$ 同理),而後對每個$f_i$作一遍FMT,點值相乘時這麼寫:$h_{i, S} = \sum\limits_{j = 0}^{i} f_{j,S} * g_{i - j, S}$。最後掃一遍把不符合實際狀況的狀態賦成 $0$便可。($bc[]$表示集合元素個數,即$bitcount$)
for i = 0 to n Fmt(f[i]) Fmt(g[i]) for s = 0 to U - 1 for j = 0 to i h[i][s] += f[j][s] * g[i - j][s] Ifmt(h[i]) for s = 0 to U - 1 h[bc[s]][s] is the real answer