1. 程式人生 > >[總結] 快速莫比烏斯變換和子集卷積

[總結] 快速莫比烏斯變換和子集卷積

馬上noip了我在學點啥

問題

求集合並卷積,即\(h_S=\sum_{L\in S}\sum_{R\in S}[L\cup R]f_L*g_R\)

要求更嚴一點,求子集卷積,即\(h_S=\sum_{L\in S}\sum_{R\in S}[L\cup R=S][L\cap R=\varnothing]f_L*g_R=\sum_{L\in S}f_L*g_{S-L}\)

Sol

先看集合並卷積

最暴力的做法就是\(O(2^n)\)分別列舉\(L,R\)\(O(4^n)\)的將答案加到\(h\)裡去 我覺得不行

下面是個高妙做法:

  • 定義\(f\)的莫比烏斯變換為\(\hat f\),其中\(\hat {f_{S}}=\sum_{T\in S}f_T\)
  • 我們對卷積式兩邊同時做莫比烏斯變換:\(\hat{h_S}=\sum_{L\in S}\sum_{R\in S}[L\cup R\in S]f_L*g_R\)
  • 由於\([L\cup R\in S]=[L\in S][R\in S]\),所以\(\hat{h_S}=\sum_{L\in S}\sum_{R\in S}f_L*g_R\)
  • \(\hat{h_S}=(\sum_{L\in S}f_L)*(\sum_{R\in S}g_R)\)

於是問題就在如何快速求出\(f\)\(g\)莫比烏斯變換。

這東西是個子集和,可以高維字首和優化到\(O(n\times 2^n)\)

但是我們求出來的只是\(\hat{h_S}\)

,我們還需要減去它的子集和,就需要再做一遍高維差分,複雜度同樣是\(O(n\times 2^n)\)

下面我們看子集卷積

它的條件比集合並卷積更苛刻,要求\(L\)\(R\)的集合不能相交。

我們可以在卷積時多加一維,維護集合的大小,如\(f_{i,S}\)表示集合中有\(i\)個元素,集合表示為\(S\)。顯然,當\(i\)\(S\)的真實元素個數相等時才是合法的。初始時,我們只把\(f_{cnt|S|,S}\)的值賦成原來的\(f_S\),然後做一遍莫比烏斯變換,\(h_{i,S}=\sum_{j=0}^if_{j,S}*g_{i-j,S}\)

還是上程式碼吧

void FMT(int *A, int o) {// o 為識別因子
    for (int i = 1; i < ST; i <<= 1)//ST-1 表示全集
        for (int j = 0; j < ST; j++)
            if (i&j) (A[j] += A[j^i]*o) %= mod;
}
for (int i = 0; i <= n; i++) FMT(g[i], 1);
for (int i = 0; i <= n; i++) FMT(f[i], 1);
for (int i = 1; i <= n; i++) {
    for (int j = 0; j <= i; j++)
        for (int k = 0; k < ST; k++)
            (h[i][k] += 1ll*f[j][k]*g[i-j][k]%mod) %= mod;
    FMT(h[i], -1);
    for (int k = 0; k < ST; k++) if (cnt[k] != i) h[i][k] = 0;
    if (i != n) FMT(h[i], 1);
}