位运算卷积与快速沃尔什变换
我们要快速计算一类形如
的问题,其中 \(\oplus\) 是 \(\operatorname{bitand},\operatorname{bitor},\operatorname{xor}\) 之一。
And 卷积 / Or 卷积
对于下标范围是 \([0,2^n-1]\) 的数列 \(a\),设
我们设 And 卷积的运算符是 \(\times\),那么:
因此,我们只需要对于 \(a\) 和 \(b\) 分别求出其 \(\mathrm{FMT}\),点积后再做 \(\mathrm{FMT}\) 的逆变换即可。
考虑这里的 \(\mathrm{FMT}\) 实际上就是高维前缀和,我们对于所有 \(i\in [0,n-1]\cap \mathbb{Z}\),在第 \(i\) 维做一遍前缀和即可。
Or 卷积是类似的。
void FMTAnd(ll (&arr)[Up],ll w){
For(j,0,n-1) For(i,0,up) if(!((i>>j)&1)){
arr[i]=(arr[i]+w*arr[i|(1<<j)])%Mod;
}
}
void FMTOr(ll (&arr)[Up],ll w){
For(j,0,n-1) For(i,0,up) if(!((i>>j)&1)){
arr[i|(1<<j)]=(arr[i|(1<<j)]+w*arr[i])%Mod;
}
}
快速沃尔什变换 FWT:Xor 卷积
一部分参考了 command_block 的博客。
Xor 没有 And 和 Or 那样优秀的性质。仍然考虑求出一个 \(\mathrm{FWT}(a)_i\),设它是
我们需要让它满足:
其中 \(\times\) 表示 Xor 卷积。
我们大力展开上式:
对比左右两边的展开式,可以得到 \(C_{i,j}C_{i,k}=C_{i,j\operatorname{xor} k}\)。
设 \(f(x,k)\) 表示 \(x\) 在二进制下的第 \(k\) 位。考虑这样的构造:先构造一个 \(2\times 2\) 的矩阵 \(c\),然后令 \(C_{i,j}=\prod_{k\ge 0} c_{f(i,k),f(j,k)}\)。如果 \(c\) 满足 \(c_{i,j}c_{i,k}=c_{i,j\operatorname{xor} k}\),那么 \(C\) 也满足。
我们也可以证明:假如 \(c\) 存在逆矩阵 \(c^{-1}\),那么 \(\mathrm{FWT}\) 的逆运算就是对 \(c^{-1}\) 做上述过程。
所以 \(c\) 需要满足:
- \(c_{0,0}c_{0,1}=c_{0,1}\);
- \(c_{1,0}c_{1,1}=c_{1,1}\);
- \(c_{0,1}c_{0,1}=c_{0,0}\);
- \(c_{1,1}c_{1,1}=c_{1,0}\);
- \(c\) 有逆矩阵。
因此,矩阵中的任意位置都不能是 \(0\)。从而推出 \(c_{0,0}=c_{1,0}=1\)。此时 \(c_{1,0},c_{1,1}\) 可以取 \(1\) 或 \(-1\),但如果 \(c_{1,0}=c_{1,1}\) 的话,\(c\) 就没有逆了。所以 \(c\) 有两种取法:
这里用第一种。可以得出它的逆矩阵是:
注
这个 \(c\) 对应的 \(\mathrm{FWT}\) 就是:
其中 \(|x|\) 代表 \(\operatorname{popcount}(x)\)。
有了 \(c\) 矩阵,我们再考虑 \(\mathrm{FWT}\) 怎么实现。考虑用一个分治的过程:设数列 \(a\) 的下标范围是 \(0\dots (2^n-1)\),当我们要算 \(\mathrm{FWT}(a)\) 时,设 \(L=a_{0\dots (2^{n-1}-1)},R=a_{2^{n-1}\dots (2^n-1)}\)。我们先算出 \(\mathrm{FWT}(L),\mathrm{FWT}(R)\),并对 \(0\le i<2^{n-1}\) 以及 \(2^{n-1}\le i<2^n\) 两种情况分别算出 \(\mathrm{FWT}(a)_i\)。
对于 \(0\le i<2^{n-1}\):
对于 \(2^{n-1}\le i<2^n\):
代码实现时可以倍增。
void FWT(ll (&arr)[Up],const ll (&c)[2][2]){
for(int len=1;len<=up;len*=2){
for(int i=0;i<=up;i+=len*2){
For(j,i,i+len-1){
int orig=arr[j];
arr[j]=(c[0][0]*arr[j]+c[0][1]*arr[j+len])%Mod;
arr[j+len]=(c[1][0]*orig+c[1][1]*arr[j+len])%Mod;
}
}
}
}