\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)\)

其它卷积

马上补。

版权声明:本文为nosta原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/nosta/p/11133787.html