1. 程式人生 > >邏輯、集合運算上的卷積一覽(FMT、FWT,……)

邏輯、集合運算上的卷積一覽(FMT、FWT,……)

\oplus=\and,\or,\veebar

簡介

對於邏輯\(\oplus\)的卷積,而且你不能N方豹草
\[ A_k=\sum_{i\oplus j=k} B_i\times C_k\\ \]
那麼嘗試構造變換\(F_{\oplus}\)和反演\(F_{\oplus}^{-1}\)使滿足
\[ F_{\oplus}(A)_k=F_{\oplus}(B)_k\times F_{\oplus}(C)_k\\ A_k=F_{\oplus}^{-1}(F_{\oplus}(A))_k \]
用來加速運算。

或與卷積

或與卷積的變換

定義或、與卷積的變換分別為
\[ F_{\or}(A)_k=\sum_{i\or k=k}A_i,F_{\and}(A)_k=\sum_{i\and k=k}A_i \]

如下驗證兩種變換的可行性
\[ \begin{aligned} F_{\or}(B)_k\times F_{\or}(C)_k &=\sum_{i\or k=k}B(i)\sum_{j\or k=k}C(j) \\&=\sum_{x\or k=k}\sum_{i\or j=x}B(i)\times C(j) \\&=\sum_{x\or k=k}A(x) \\&=F_{\or}(A)_k \end{aligned} \begin{aligned} F_{\and}(B)_k\times F_{\and}(C)_k &=\sum_{i\and k=k}B(i)\sum_{j\and k=k}C(j) \\&=\sum_{x\and k=k}\sum_{i\and j=x}B(i)\times C(j) \\&=\sum_{x\and k=k}A(x) \\&=F_{\and}(A)_k \end{aligned} \]

驗證成功。

如何實現這兩種變換?注意到如果將\(n\)位二進位制數域對映到一個\(n\)維空間,則\(F_{\or}(A)_i\)相當於在空間內求高維字首和,\(F_{\and}(A)\)則是求高維字尾和。

因此直接上高維前/字尾和就能做到\(O(n2^n)\)的複雜度,這樣的做法屬於“快速莫比烏斯變換”。

void FMT_OR(int a[],int len) {
    int n=__builtin_ctz(len);
    for(int i=0; i<n; ++i)
        for(int j=0; j<len; ++j) if((j>>i)&1)
            a[j]+=a[j^(1<<i)];
}
void FMT_AND(int a[],int len) {
    int n=__builtin_ctz(len);
    for(int i=0; i<n; ++i)
        for(int j=len-1; ~j; --j) if((j>>i)&1)
            a[j^(1<<i)]+=a[j];
}

還有一種通用的方法:“快速沃爾什變換”,複雜度同上。

我們把問題劃為n個階段編號0到n-1,在第i個階段中,把序列劃為\(\frac{2^n}{2^i}\)個區間,並記\(F_{\oplus}(A)_{i,x}\)
表示x所在區間中所有下標與x就二進位制末i+1位滿足特定規則的元素累和。

例如\(F_{\oplus}(A)_{0,x}=A_x\),而所求\(F_{\oplus}(A)_x=F_{\oplus}(A)_{n-1,x}\)。

從階段i轉移到階段i+1時,階段i+1的一個區間內的答案顯然由階段i中位置對應的相鄰兩個區間內的答案轉移而來,此時決策為二進位制第i+2末位的取與不取,即從\(F_{\oplus}(A)_{i,l+x}\)和\(F_{\oplus}(A)_{i,l+2^i+x}\)轉移到\(F_{\oplus}(A)_{i+1,l+x}\)和\(F_{\oplus}(A)_{i+1,l+2^i+x}\),其中l是階段i+1中的某個區間的左端點。

轉移按照變換式針對這四個變數做就好了。實現如下

void FWT_OR(int a[],int len) {
    int n=__builtin_ctz(len);
    for(int m=1; m<len; m<<=1)
        for(int i=0,s=m<<1; i<len; i+=s)
            for(int j=0; j<m; ++j) a[m+i+j]+=a[i+j];
}
void FWT_AND(int a[],int len) {
    for(int m=1; m<len; m<<=1)
        for(int i=0,s=m<<1; i<len; i+=s)
            for(int j=0; j<m; ++j) a[i+j]+=a[m+i+j];
}

或與卷積的反演

前/字尾和的反演還能怎麼求……
\[ F_{\or}^{-1}(A)_k=\sum_{i\or k=k} (-1)^{|k|-|i|}F_{\or}(A)_k\\ F_{\and}^{-1}(A)_k=\sum_{i\and k=k} (-1)^{|i|-|k|}F_{\and}(A)_k \]
其中\(|i|\)是將\(i\)的二進位制上\(1\)的個數。

先來“快速莫比烏斯反演”做法,直接把變換逆過來做

void IFMT_OR(int a[],int len) {
    int n=__builtin_ctz(len);
    for(int i=0; i<n; ++i)
        for(int j=len-1; ~j; --j) if((j>>i)&1)
            a[j]-=a[j^(1<<i)];
}
void IFMT_AND(int a[],int len) {
    int n=__builtin_ctz(len);
    for(int i=0; i<n; ++i)
        for(int j=0; j<len; ++j) if((j>>i)&1)
            a[j^(1<<i)]-=a[j];
}

然後是“快速沃爾什反演”做法,步驟與變換類似,只是累和改為消除。

void IFWT_OR(int a[],int len) {
    int n=__builtin_ctz(len);
    for(int m=1; m<len; m<<=1)
        for(int i=0,s=m<<1; i<len; i+=s)
            for(int j=0; j<m; ++j) a[m+i+j]-=a[i+j];
}
void IFWT_AND(int a[],int len) {
    for(int m=1; m<len; m<<=1)
        for(int i=0,s=m<<1; i<len; i+=s)
            for(int j=0; j<m; ++j) a[i+j]-=a[m+i+j];
}

異或卷積

異或卷積的變換

定義異或卷積的變換為
\[ F_{\veebar}(A)_k=\sum_{i=0} (-1)^{|i\and k|}A_i \]

這次不去驗證,考慮直接推導【膜rockdu】,首先假定變換\(F_{\veebar}(A)\)與\(A\)線性相關,如下,
\[ F_{\veebar}(A)_k=\sum_{i=0} g(k,i)A_i \]
當然\(g(,)\)是需要能支援反演的,因此\(g(,)=0\)之類的就不考慮了。那麼
\[ \begin{aligned} F_{\veebar}(A)_k&=\sum_{i=0}g(k,i)A_i=\sum_{i=0}g(k,i)\sum_{p\veebar q=i}B_pC_q \\&=\sum_{i=0}\sum_{j=0}g(k,i\veebar j)B_iC_j \\ F_{\veebar}(B)_k\times F_{\veebar}(C)_k &=\sum_{i=0}g(k,i)B_i\sum_{j=0}g(k,j)C_j \\&=\sum_{i=0}\sum_{j=0}g(k,i)g(k,j)B_iC_j \\ g(k,i\veebar j)&=g(k,i)\times g(k,j) \end{aligned} \]
我們需要構造一個\(g(,)\)。

注意\(|i\veebar j|=|i|+|j|\pmod2\),以及\((i\veebar j)\and k=(i\and k)\veebar (j\and k)\),那麼
\[ |(i\veebar j)\and k|=|(i\and k)\veebar(i\and k) |=|i\and k|+|j\and k|\pmod2\\ (-1)^{|(i\veebar j)\and k|}=(-1)^{|i\and k|}(-1)^{|j\and k|} \]
因此令\(g(k,i)=(-1)^{|i\and k|}\)就能得到一個合法變換
\[ F_{\veebar}(A)_k=\sum_{i=0}(-1)^{|i\and k|}A_i \]
如何實現這種變換?高維前/字尾和似乎已經G了,使用快速沃爾什變換,相鄰兩個階段轉移,要討論對下標與的二進位制1的個數的影響,結果如下

\[ F_{\veebar}(A)_{i+1,l+x}=F_{\veebar}(A)_{i,l+x}+F_{\veebar}(A)_{i,l+2^i+x}\\ F_{\veebar}(A)_{i+1,l+2^i+x}=F_{\veebar}(A)_{i,l+x}-F_{\veebar}(A)_{i,l+2^i+x} \]
那麼變換就完成了

void FWT_XOR(int a[],int len) {
    int n=__builtin_ctz(len);
    for(int m=1; m<len; m<<=1)
        for(int i=0,s=m<<1; i<len; i+=s)
            for(int j=0; j<m; ++j) {
                int x=f[i+j], y=f[m+i+j];
                f[i+j]=x+y;
                f[m+i+j]=x-y;
            }
}

異或卷積的反演

反演時的扣除貢獻的式子就是把累和的式子反解

\[ F_{\veebar}(A)_{i,l+x}=\frac{F_{\veebar}(A)_{i+1,l+x}+F_{\veebar}(A)_{i+1,l+2^i+x}}2\\ F_{\veebar}(A)_{i,l+2^i+x}=\frac{F_{\veebar}(A)_{i+1,l+x}-F_{\veebar}(A)_{i+1,l+2^i+x}}2\\ \]

實現如下

void FWT_XOR(int a[],int len) {
    int n=__builtin_ctz(len);
    for(int m=1; m<len; m<<=1)
        for(int i=0,s=m<<1; i<len; i+=s)
            for(int j=0; j<m; ++j) {
                int x=f[i+j], y=f[m+i+j];
                f[i+j]=(x+y)/2;
                f[m+i+j]=(x-y)/2;
            }
}

混合卷積

子集卷積

要求卷積

\[ A_k=\sum_{i\or k=k}\sum_{j\or k=k} [j\and k=0] B_iC_j \\=\sum_{i\or k=k}\sum_{j\or k=k} [|i|+|j|=|k|] B_iC_j \]

可以列舉補充一維集合大小,從小到大列舉集合大小,分別做一次或卷積,時間複雜度\(O(n^22^n)\)。

其它卷積

馬上補