快速莫比乌斯/沃尔什变换 (FMT/FWT)

快速莫比乌斯/沃尔什变换 (FMT/FWT)

这个东西是用来求 二进制位运算 的卷积的,or卷积、and卷积、xor卷积。

引入

我们要求的是:

C[i]=i=jkA[j]B[k]

考虑像 FFT 一样,先将一个式子计算出它的正变换后的式子,再相乘,最后做一次逆变换。于是我们先定义一个式子:

fwtA[i]=j=0nc(i,j)A[j]

其中,fwtA 表示 A 经过 FWT 变换后的数组,c(i,j) 表示一个贡献函数,现在还没有求出来。

然后,我们需要它满足(后面那个是按位乘):

C=ABfetC=fwtA×fwtB

接着,我们继续推:

fetC[i]=i=0nfwtA[i]fwtB[i]i=0nc(i,j)C[j]=i=0n(j=0nc(i,j)A[j])(sumk=0nc(i,k)B[k])i=0nc(i,p)p=jkA[j]B[k]=i=0nj=0nk=0nc(i,j)c(i,k)A[j]B[k]i=0np=jkc(i,jk)A[j]B[k]=i=0nj=0nk=0nc(i,j)c(i,k)A[j]B[k]

于是我们可以得到 c(i,j)c(i,k)=c(i,jk) ,又因为它是位运算,所以我们可以把它按位拆开来处理。并且对于一个数,我们将它 每一位拆开后的数 的贡献系数乘起来的结果作为它的贡献系数。

接下来我们继续考虑求这个式子;

fwtA[i]=j=0nc(i,j)A[j]

考虑像 FFT 一样拆成两半,设 A0 表示序列 A 中,下标二进制最高位的值为 0;A1 同理。那么我们可以得到这样的式子:

fwtA[i]=j=0n/2c(i,j)A[j]+j=n/2+1nc(i,j)A[j]

然后我们对首位拆开来考虑,设 i 表示 i 去掉首位后的值:

=c(i0,0)j=0n/2c(i,j)A[j]+c(i0,1)j=n/2+1nc(i,j)A[j]

接着分类讨论 i 的首位取 0 还是 1。

fwtA[i]=c(0,0)fwtA0[i]+c(0,1)fwtA1[i]fwtA[i+n/2]=c(1,0)fwtA0[i]+c(1,1)fwtA1[i]

最终我们得到了这样的式子,与 FFT 类似,我们可以分治。对于求出 c([0,1],[0,1]) ,我们可以使用矩阵,或者再去推式子。把这些写出来的原因是,我们可以使用矩阵求逆,求出逆变换时的系数来做逆变换,自己并不是很能理解用其他方法来计算逆运算时的系数。

OR卷积

对于 or卷积,我们要求的就是:

fwtC[i]=j|i=iC[j]

而:

fwtA[i]fwtB[i]=(j|i=iA[j])(k|i=iB[k])=(j|k)|i=iA[j]B[k]=(j|k)|i=iC[j|k]=fwtC[i]

接着我们考虑前面那个分治的式子:

fwtA[i]=c(0,0)fwtA0[i]+c(0,1)fwtA1[i]fwtA[i+n/2]=c(1,0)fwtA0[i]+c(1,1)fwtA1[i]

此时我们容易发现,对于 fwtA1[i] ,它无法通过 or 运算得到 in/2 ,所以 c(0,1) 应该为 0;而其他的都可以转移到,都为 1。

然后我们再通过求矩阵的逆 ,可以得到矩阵 [1011] 的逆为:[1011]

所以 or卷积 的正变换式子为:

fwtA[i]=fwtA0[i]fwtA[i+n/2]=fwtA0[i]+fwtA1[i]

逆变换式子为:

IfwtA[i]=IfwtA0[i]IfwtA[i+n/2]=IfwtA1[i]IfwtA0[i]

最终我们把系数代入,分治求答案就好了。

AND卷积

or卷积 类似,定义式子 fwtA[i]=j&i=iA[j] ,我们容易得到 fwtA[i]fwtB[i]=fwtC[i] 。然后考虑贡献系数:

fwtA[i]=c(0,0)fwtA0[i]+c(0,1)fwtA1[i]fwtA[i+n/2]=c(1,0)fwtA0[i]+c(1,1)fwtA1[i]

此时 i 不可能通过 and运算 得到 i+n/2 ,所以此时的 c(1,0) 应为 0,其余为 1 。所以我们得到矩阵 [1101] ,对其求逆可得 [1101] 。于是我们得到了正变换的式子:

fwtA[i]=fwtA0[i]+fwtA1[i]fwtA[i+n/2]=fwtA1[i]

逆变换的式子:

IfwtA[i]=IfwtA0[i]IfwtA1[i]IfwtA[i+n/2]=IfwtA1[i]

XOR卷积

这部分使用 表示 按位异或 运算。

我们根据 c(i,j)c(i,k)=c(i,jk) 来推导贡献系数。

根据 c(0,0)c(0,x)=c(0,x0)=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)=±1 ,而如果 c(1,0)=1 ,则 c(1,1) 无实数解,所以我们令 c(1,0)=1 ,而 c(1,1)=±1

由于 c(0,1)c(0,1)=c(0,0) ,所以 c(0,1)=±1

而当 c(0,1)=c(1,1) 时,矩阵没有逆。所以两个取异号。我们这里使用 c(0,1)=1,c(1,1)=1

然后我们就能够构造出转移系数的矩阵 [1111] 了,对其求逆得到 [0.50.50.50.5]

正变换式子:

fwtA[i]=fwtA0[i]+fwtA1[i]fwtA[i+n/2]=fwtA0[i]fwtA1[i]

逆变换式子:

IfwtA[i]=IfwtA0[i]+IfwtA1[i]2IfwtA[i+n/2]=IfwtA0[i]IfwtA1[i]2

最终,我们得出了以上三种卷积的 O(nlogn) 的计算方法。

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;
}
posted @   Cyan_wind  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示