快速莫比乌斯/沃尔什变换 (FMT/FWT)
基本概念
定义
本质就是全集的各个子集到值域的映射。
形式化地来说就是,域
从多项式的角度理解,就是把之前的下标为某个数字改为为某个具体集合。
表示
我们用
加法与乘法
加法直接对应系数相加即可,注意此时
乘法
我们设
根据
集合并卷积 / OR 卷积
就是把上述
莫比乌斯变换(FMT)
定义快速莫比乌斯变换,
定义快速莫比乌斯反演,
其中 FMT 和 FMI 互为逆变换。对于 OR 卷积,我们只需要把
莫比乌斯变换可以通过高维前缀和来实现,所以我们对于 FMT 就是做一个系数为
莫比乌斯变换的本质是容斥原理,原先
沃尔什变换(FWT)
对于卷积
发现以上都可以通过
进行如下变换,
变换之后,递归求
很容易写出一个分治的形式,但是常数很大,我们将其改成非递归形式。
容易发现其实往下递归的时候是
为了模拟递归,我们先要枚举递归层数,也就是正在处理的这一位
for(int k=1;k<full;k<<=1)
for(int i=0;i<full;i+=(k<<1))
for(int j=0;j<k;j++)
add(f[i|j|k],1ll*f[i|j]*t%mod);
集合交卷积 / AND 卷积
就是把上述
不管是理解还是推导方面,都和 OR 卷积是一样的,就不过多赘述了。
莫比乌斯变换(FMT)
把 OR 卷积中系数为
容斥的时候钦定
沃尔什变换(FWT)
也是几乎和 OR 卷积一模一样。
先进行 FWT,
再进行 IFWT,
集合对称差卷积 / XOR 卷积
就是把上述
暴力计算时间复杂度
沃尔什变换(FWT)
定义沃尔什变换
定义沃尔什逆变换,
对于卷积
发现以上都可以通过
进行如下变换,
变换之后,递归求
将上面这个递归形式改成非递归形式就行了。
void FWT(int *f,bool type){
for(int k=1;k<full;k<<=1)
for(int i=0;i<full;i+=(k<<1))
for(int j=0;j<k;j++){
int x=f[i|j],y=f[i|j|k];
f[i|j]=(x+y)%mod; f[i|j|k]=(x-y+mod)%mod;
}
if(!type) return ;
for(int i=0;i<full;i++) f[i]=1ll*f[i]*invp%mod;
}
线性代数角度的 FWT
我们设
由于变换后对应位置相乘需要满足
按位或矩阵
其逆矩阵为
按位与矩阵
其逆矩阵为
按位异或矩阵
其逆矩阵为(为了减少常数,可以把
其实很好理解,根据之前的推导,OR 卷积和 AND 卷积的变换应该是要求一个是对于子集求和,一个是对于超集求和。或矩阵中的
从这个角度,我们可以看出 FWT 是一种线性变换。
扩展 三进制 MEX 卷积
这一部分旨在启发构造一些变式位运算卷积的方法。
定义三进制
\operatorname{mex} 运算,先将两个数字a,b 进行三进制拆分,形如a=\sum a_i3^i 。\operatorname{mex}(a,b)=\sum\limits_{i=0}^{k-1}\operatorname{mex}(a_i,b_i) 。也就是说把每个数都拆成三进制表示,然后对于每个三进制位进行\operatorname{mex} 。 定义三进制\operatorname{mex} 卷积为h_s=\sum\limits_{\operatorname{mex}(L,R)=S}f_L\times g_S 给定长度为
3^{n} 的f 和g ,求解h 。n\le 10 。
还是按照 FWT 那套方法,对于当前的最高位的值进行分类。
发现我们需要构造四种乘法,
这样子递归是
逆变换,令上述分别为
可以递归计算,时间复杂度是
Code
代码中的 OR 卷积和 AND 卷积都是用 FMT 实现,XOR 卷积用 FWT 实现。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=998244353;
const int maxn=19;
const int maxs=(1<<19);
int n,full,a[maxs],b[maxs],invp;
void add(int &x,int y){ x=x+y>=mod?x+y-mod:x+y; }
void sub(int &x,int y){ x=x-y<0?x-y+mod:x-y; }
void FMT(int* f,int t){
for(int i=1;i<full;i<<=1)
for(int j=0;j<full;j++)
if(i&j) add(f[j],1ll*t*f[i^j]%mod);
}
void FMT2(int* f,int t){
for(int i=1;i<full;i<<=1)
for(int j=0;j<full;j++)
if(!(i&j)) add(f[j],1ll*t*f[i^j]%mod);
}
void FWT(int *f,bool type){
for(int k=1;k<full;k<<=1)
for(int i=0;i<full;i+=(k<<1))
for(int j=0;j<k;j++){
int x=f[i|j],y=f[i|j|k];
f[i|j]=(x+y)%mod; f[i|j|k]=(x-y+mod)%mod;
}
if(!type) return ;
for(int i=0;i<full;i++) f[i]=1ll*f[i]*invp%mod;
}
void print(int *H){
for(int i=0;i<full;i++) cout<<H[i]<<" ";
cout<<endl;
}
int A[maxs],B[maxs],C[maxs];
void or_conv(){
memcpy(A,a,sizeof(a)); memcpy(B,b,sizeof(b));
FMT(A,1); FMT(B,1);
for(int i=0;i<full;i++) C[i]=1ll*A[i]*B[i]%mod;
FMT(C,mod-1); print(C);
}
void and_conv(){
memcpy(A,a,sizeof(a)); memcpy(B,b,sizeof(b));
FMT2(A,1); FMT2(B,1);
for(int i=0;i<full;i++) C[i]=1ll*A[i]*B[i]%mod;
FMT2(C,mod-1); print(C);
}
void xor_conv(){
memcpy(A,a,sizeof(a)); memcpy(B,b,sizeof(b));
FWT(A,0); FWT(B,0);
for(int i=0;i<full;i++) C[i]=1ll*A[i]*B[i]%mod;
invp=1; for(int i=1;i<=n;i++) invp=1ll*invp*((mod+1)>>1)%mod;
FWT(C,1); print(C);
}
int main(){
cin>>n; full=(1<<n);
for(int i=0;i<full;i++) cin>>a[i];
for(int i=0;i<full;i++) cin>>b[i];
or_conv(); and_conv(); xor_conv();
return 0;
}