Loading

位运算卷积与快速沃尔什变换

我们要快速计算一类形如

\[c_i=\sum_{j\oplus k=i} a_jb_k \]

的问题,其中 \(\oplus\)\(\operatorname{bitand},\operatorname{bitor},\operatorname{xor}\) 之一。

And 卷积 / Or 卷积

对于下标范围是 \([0,2^n-1]\) 的数列 \(a\),设

\[\mathrm{FMT}(a)_i=\sum_{j\operatorname{bitand} i=i} a_j. \]

我们设 And 卷积的运算符是 \(\times\),那么:

\[\begin{aligned} \mathrm{FMT}(a\times b)_i&=\sum_{j\operatorname{bitand} i=i} (a\times b)_j\\ &=\sum_{j\operatorname{bitand} i=i} \sum_{x\operatorname{bitand} y=j} a_xb_y\\ &=\sum_{x\operatorname{bitand} y\operatorname{bitand} i=i} a_xb_y\\ &=\mathrm{FMT}(a)_i\cdot \mathrm{FMT}(b)_i. \end{aligned} \]

因此,我们只需要对于 \(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\),设它是

\[\mathrm{FWT}(a)_i=\sum_{j} C_{i,j}a_j. \]

我们需要让它满足:

\[\mathrm{FWT}(a)\cdot \mathrm{FWT}(b)=\mathrm{FWT}(a\times b). \]

其中 \(\times\) 表示 Xor 卷积。

我们大力展开上式:

\[\begin{aligned} \mathrm{FWT}(a)_i\cdot \mathrm{FWT}(b)_i&=\sum_j C_{i,j}a_j\sum_{k} C_{i,k}b_k.\\ \mathrm{FWT}(a\times b)_i&=\sum_j C_{i,j} (a\times b)_j\\ &=\sum_j C_{i,j} \sum_{x\operatorname{xor} y=j} a_xb_y\\ &=\sum_{j,k} a_jb_kC_{i,j\operatorname{xor} k}. \end{aligned} \]

对比左右两边的展开式,可以得到 \(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\) 有两种取法:

\[\begin{bmatrix} 1 & 1\\ 1 & -1 \end{bmatrix}, \begin{bmatrix} 1 & -1\\ 1 & 1 \end{bmatrix}. \]

这里用第一种。可以得出它的逆矩阵是:

\[\begin{bmatrix} \frac{1}{2} & \frac{1}{2}\\ \frac{1}{2} & -\frac{1}{2} \end{bmatrix}. \]

这个 \(c\) 对应的 \(\mathrm{FWT}\) 就是:

\[\mathrm{FWT}(a)_i=\sum_{j} (-1)^{|i\operatorname{bitand} j|} a_j. \]

其中 \(|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}\)

\[\mathrm{FWT}(a)_i=c_{0,0}\mathrm{FWT}(L)_i+c_{0,1}\mathrm{FWT}(R)_i. \]

对于 \(2^{n-1}\le i<2^n\)

\[\mathrm{FWT}(a)_i=c_{1,0}\mathrm{FWT}(L)_{i-2^{n-1}}+c_{1,1}\mathrm{FWT}(R)_{i-2^{n-1}}. \]

代码实现时可以倍增。

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;
			}
		}
	}
}
posted @ 2022-07-25 23:44  Alan_Zhao_2007  阅读(101)  评论(2编辑  收藏  举报