快速莫比乌斯/沃尔什变换 (FMT/FWT)
快速莫比乌斯/沃尔什变换 (FMT/FWT)
这个东西是用来求 二进制位运算 的卷积的,\(or\)卷积、\(and\)卷积、\(xor\)卷积。
引入
我们要求的是:
考虑像 FFT 一样,先将一个式子计算出它的正变换后的式子,再相乘,最后做一次逆变换。于是我们先定义一个式子:
其中,\(fwt_A\) 表示 \(A\) 经过 \(FWT\) 变换后的数组,\(c(i,j)\) 表示一个贡献函数,现在还没有求出来。
然后,我们需要它满足(后面那个是按位乘):
接着,我们继续推:
于是我们可以得到 \(c(i,j)*c(i,k)=c(i,j\oplus k)\) ,又因为它是位运算,所以我们可以把它按位拆开来处理。并且对于一个数,我们将它 每一位拆开后的数 的贡献系数乘起来的结果作为它的贡献系数。
接下来我们继续考虑求这个式子;
考虑像 FFT 一样拆成两半,设 \(A_0\) 表示序列 \(A\) 中,下标二进制最高位的值为 0;\(A_1\) 同理。那么我们可以得到这样的式子:
然后我们对首位拆开来考虑,设 \(i'\) 表示 \(i\) 去掉首位后的值:
接着分类讨论 \(i\) 的首位取 0 还是 1。
最终我们得到了这样的式子,与 FFT 类似,我们可以分治。对于求出 \(c([0,1],[0,1])\) ,我们可以使用矩阵,或者再去推式子。把这些写出来的原因是,我们可以使用矩阵求逆,求出逆变换时的系数来做逆变换,自己并不是很能理解用其他方法来计算逆运算时的系数。
OR卷积
对于 \(or\)卷积,我们要求的就是:
而:
接着我们考虑前面那个分治的式子:
此时我们容易发现,对于 \(fwt_{A_1}[i]\) ,它无法通过 or 运算得到 \(i-n/2\) ,所以 \(c(0,1)\) 应该为 0;而其他的都可以转移到,都为 1。
然后我们再通过求矩阵的逆 ,可以得到矩阵 \(\begin{bmatrix} 1&0 \\ 1&1 \end{bmatrix}\) 的逆为:\(\begin{bmatrix} 1&0 \\ -1&1 \end{bmatrix}\) 。
所以 \(or\)卷积 的正变换式子为:
逆变换式子为:
最终我们把系数代入,分治求答案就好了。
AND卷积
与 \(or\)卷积 类似,定义式子 \(fwt_A[i]=\sum_{j\&i=i}A[j]\) ,我们容易得到 \(fwt_A[i]*fwt_B[i]=fwt_C[i]\) 。然后考虑贡献系数:
此时 \(i\) 不可能通过 and运算 得到 \(i+n/2\) ,所以此时的 \(c(1,0)\) 应为 0,其余为 1 。所以我们得到矩阵 \(\begin{bmatrix} 1&1 \\ 0&1 \end{bmatrix}\) ,对其求逆可得 \(\begin{bmatrix} 1&-1 \\ 0&1 \end{bmatrix}\) 。于是我们得到了正变换的式子:
逆变换的式子:
XOR卷积
这部分使用 \(\oplus\) 表示 按位异或 运算。
我们根据 \(c(i,j)*c(i,k)=c(i,j\oplus k)\) 来推导贡献系数。
根据 \(c(0,0)*c(0,x)=c(0,x\oplus0)=c(0,x)\) ,我们可以得到 \(c(0,0)=1\) ;
由于 \(c(1,1)*c(1,1)=c(1,0)\) ,此时如果填 0,那么矩阵没有逆,所以这两个数都非 0;
又因为 \(c(1,0)*c(1,0)=c(1,0)\) ,所以此时的 \(c(1,0)=\pm1\) ,而如果 \(c(1,0)=-1\) ,则 \(c(1,1)\) 无实数解,所以我们令 \(c(1,0)=1\) ,而 \(c(1,1)=\pm1\) 。
由于 \(c(0,1)*c(0,1)=c(0,0)\) ,所以 \(c(0,1)=\pm1\) 。
而当 \(c(0,1)=c(1,1)\) 时,矩阵没有逆。所以两个取异号。我们这里使用 \(c(0,1)=1,c(1,1)=-1\) 。
然后我们就能够构造出转移系数的矩阵 \(\begin{bmatrix} 1&1 \\ 1&-1 \end{bmatrix}\) 了,对其求逆得到 \(\begin{bmatrix} 0.5&0.5 \\ 0.5&-0.5 \end{bmatrix}\) 。
正变换式子:
逆变换式子:
最终,我们得出了以上三种卷积的 \(O(n\log n)\) 的计算方法。
Code
#include<cstdio>
using namespace std;
const int N=1e6+5,M=998244353,NY=499122177;
long long a[N],b[N],c[N],ansa[N],ansb[N],ansc[N];
int n;
void OR(int flag)
{
int i,j,len,p;
for(len=2,p=1;len<=n;len=len<<1,p=p<<1)
{
for(i=0;i<n;i+=len)
{
for(j=0;j<p;j++)
c[i+j+p]=(c[i+j+p]+c[i+j]*flag+M)%M;
}
}
}
void get_OR()
{
int i;
for(i=0;i<n;i++)
c[i]=a[i];
OR(1);
for(i=0;i<n;i++)
{
ansa[i]=c[i];c[i]=b[i];
}
OR(1);
for(i=0;i<n;i++)
c[i]=ansa[i]*c[i]%M;
OR(-1);
for(i=0;i<n;i++)
ansa[i]=c[i];
}
void AND(int flag)
{
int i,j,len,p;
for(len=2,p=1;len<=n;len=len<<1,p=p<<1)
{
for(i=0;i<n;i+=len)
{
for(j=0;j<p;j++)
c[i+j]=(c[i+j+p]*flag+c[i+j]+M)%M;
}
}
}
void get_AND()
{
int i;
for(i=0;i<n;i++)
c[i]=a[i];
AND(1);
for(i=0;i<n;i++)
{
ansb[i]=c[i];c[i]=b[i];
}
AND(1);
for(i=0;i<n;i++)
c[i]=ansb[i]*c[i]%M;
AND(-1);
for(i=0;i<n;i++)
ansb[i]=c[i];
}
void XOR(long long x)
{
int i,j,len,p;long long t;
for(len=2,p=1;len<=n;len=len<<1,p=p<<1)
{
for(i=0;i<n;i+=len)
{
for(j=0;j<p;j++)
{
t=c[i+j+p];
c[i+j+p]=(c[i+j]-t+M)*x%M;
c[i+j]=(c[i+j]+t)*x%M;
}
}
}
}
void get_XOR()
{
int i;
for(i=0;i<n;i++)
c[i]=a[i];
XOR(1);
for(i=0;i<n;i++)
{
ansc[i]=c[i];c[i]=b[i];
}
XOR(1);
for(i=0;i<n;i++)
c[i]=ansc[i]*c[i]%M;
XOR(NY);
for(i=0;i<n;i++)
ansc[i]=c[i];
}
int main()
{
int i;
scanf("%d",&n);
n=(1<<n);
for(i=0;i<n;i++)
scanf("%lld",&a[i]);
for(i=0;i<n;i++)
scanf("%lld",&b[i]);
get_OR();
get_AND();
get_XOR();
for(i=0;i<n;i++)
printf("%lld ",ansa[i]);
printf("\n");
for(i=0;i<n;i++)
printf("%lld ",ansb[i]);
printf("\n");
for(i=0;i<n;i++)
printf("%lld ",ansc[i]);
return 0;
}