FMT 與 子集(逆)卷積


本文參考了 Dance of Faith 大佬的博客

我們定義集合並卷積

\[h_{S} = \sum_{L \subseteq S}^{} \sum_{R \subseteq S}^{} [L \cup R = S] f_{L} * g_{R} \]

最暴力的時候只能 \(O(4^n)\) 完成,進行 一些優化 可以在 \(O(3^n)\) 內完成,當然我們可以在 \(O(n 2^n)\) 利用 \(FMT\) 或者 \(FWT\) 內快速處理。

\(FMT\) 原理更好理解,就介紹此種方式。

具體來說,類似與 \(FFT\) 我們把 \(f,g\) 求點值,然后點值相乘后再插值變化回去。因為要快速實現這個過程,我們可以考慮利用 快速莫比烏斯變換快速莫比烏斯反演 實現。

定義

定義 \(f\) 的莫比烏斯變換 \(\hat{f}\) ,滿足 \(\hat{f_S} = \sum_{T \subseteq S} f_T\) 。(也就是 \(\hat{f}\) 是原來函數 \(f\) 的子集和)

定義 \(\hat{f}\) 的莫比烏斯反演 \(f\) ,利用容斥原理可以得到 \(f_S = \sum_{T \subseteq S} (-1)^{|S| - |T|} f_{T}\)

考慮為什么這個形式滿足集合並卷積。

\[h_{S} = \sum_{L \subseteq S}^{} \sum_{R \subseteq S}^{} [L \cup R = S] f_{L} * g_{R} \]

左右同時做莫比烏斯變換。

\[\begin{aligned} \hat{h_{S}} &= \sum_{T \subseteq S} \sum_{L \subseteq T} \sum_{R \subseteq T} [L \cup R = T] f_{L} * g_{R}\\ &= \sum_{L \subseteq S} \sum_{R \subseteq S} [L \cup R \subseteq S] f_{L} * g_{R} \end{aligned} \]

由於 \([L \cup R \subseteq S] = [L \subseteq S][R \subseteq S]\) ,也就有

\[\begin{aligned} \hat{h_{S}} &= \sum_{L \subseteq S} \sum_{R \subseteq S} f_{L} * g_{R}\\ &= (\sum_{L \subseteq S}f_L)(\sum_{L \subseteq S}g_R)\\ &=\hat{f_L} * \hat{g_R} \end{aligned} \]

證畢。

快速變換與反演

原理講解

上面那兩個集合和我們暴力做是 \(O(3^n)\) 的,可以進行優化。

考慮按 集合大小 分層遞推。

\(\hat{f_{S}}^{(i)}\)\(\sum_{T \subseteq S} [(S - T) \subseteq \{0, 1, 2, ..., i\}] f_{T}\) ,有 \(\hat{f}_S^{(0)} = f_S\)

那么對於不包含 \(\{i\}\) 的集合 \(S\) ,滿足 \(\hat{f_{S}}^{(i)} = \hat{f_{S}}^{(i - 1)}\) ,那么它的貢獻就是

\[\hat{f}_{S \cup \{i\}}^{(i)} = \hat{f}_{S}^{(i - 1)} + \hat{f}_{S \cup \{i\}}^{(i - 1)} \]

這樣,遞推 \(n\) 輪后 \(\hat{f}^{(n)}_S\) 就包含所有情況,即為所求變換。

對於莫比烏斯反演的話,同樣遞推,不斷減掉就行了。

代碼實現

void FMT(int *f, int opt) {
    for (int j = 0; j < n; ++ j)
        for (int i = 0; i < (1 << n); ++ i) if (i >> j & 1)
            f[i] += opt * f[i ^ (1 << j)];
}

子集卷積

原理講解

我們定義 \(f\)\(g\) 的子集卷積 \(f * g = h\)

\[h_{S} = \sum_{L \subseteq S}^{} \sum_{R \subseteq S}^{} [L \cup R = S, L \cap R = \varnothing] f_{L} * g_{R} \]

其實就是

\[h_S = \sum_{T \subseteq S} f_T * g_{S - T} \]

剛剛講的子集並卷積為何不行呢?因為有 \([L \cap R = \varnothing]\) 的這個限制。

如何去掉這個限制呢,我們多記一維集合大小,也就是 \(f_{i, S}\) 表示有 \(i\) 個元素,集合表示為 \(S\)

顯然當且僅當 \(i = |S|\) 時是正確的,我們先做 \(FMT\)

所以遞推的時候就是 \(h_{i + j, S} = \sum_{i, j} f_{i, S} * g_{j, S}\) ,其實是個多項式乘法。

由於前面對於 \(n\) 個函數進行 \(FMT\) 需要 \(O(n^2 2^n)\) 的復雜度。

這里 \(FFT\) 也優化不了復雜度(而且由於常數會慢很多),那么直接暴力卷積就好了。

代碼實現

for (int i = 0; i <= n; ++ i) {
    for (int j = 0; i + j <= n; ++ j)
        for (int S = 0; S < (1 << n); ++ S)
        	h[i + j][S] += f[i][S] * g[j][S];
}

最后我們要求 \(S\) 的子集卷積的結果,直接用 \(h_{\text{bitcount(S)}, S}\) \(IFMT\) 的。

子集逆卷積

原理講解

我們有時候需要做子集卷積意義下的除法或者相關運算。

比如對於兩個函數 \(f * g = h\) ,我們已知 \(g, h\) 要求 \(f\)

那么就是 \(f = h * g^{-1}\) 。其實就是要求 \(g^{-1}\) 在子集卷積意義下的結果。

同樣我們只需要考慮 \(FMT\) 之后的結果。

由於對於每一位我們做的是多項式下的乘法。其實就是對於每一位求的 \(g^{-1}\) 就是多項式求逆后的結果。

同樣對於別的運算也是多項式后的結果。

但是寫那個 \(O(n \log n)\) 的倍增多項式求逆顯然十分地麻煩,可以考慮 \(O(n ^ 2)\) 遞推。

\(g^{-1} = t\) 假設我們求出 \(t_{0, 1, \cdots, i - 1}\) ,要求 \(t_i\)\((t_0 = g_0^{-1})\)

我們可以把 \(g_i\) 減去 \(t\)\(g\)\(i - 1\) 項做卷積后的第 \(i\) 項的值,就是所求的 \(t_i\)

這樣就可以解決啦qwq

代碼

inline void Inv(int *f) {
	tmp[0] = fpm(f[0], Mod - 2);
    for (int i = 0; i <= n; ++ i) {
        inv[i] = tmp[i];
        for (int j = 0; i + j <= n; ++ j)
            tmp[i + j] -= inv[i] * f[j];
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM