FWT 学习笔记

FWT 学习笔记

我们要计算如下式子,其中 \(\otimes\) 是一种位运算,这个式子又叫做位运算卷积。

\[C_i=\sum _{j\otimes k=i} A_j\times B_k \]

暴力枚举可以做到 \(O(n^2)\),可以使用 \(FWT\) 做到 \(O(n\log n)\)

类似于 FFT 考虑如下过程。

  • \(A,B\) 通过 FWT 变换成 \(A',B'\)\(O(n\log n)\)
  • 计算 \(C'=A'\times B'\),其中 $\times $ 表示逐项相乘。\(O(n)\)
  • \(C'\) 通过 FWT 逆变换成 \(C\)\(O(n\log n)\)

下文中的一些记号约定:\(\lor\) 表示按位或,\(\land\) 表示按位与,\(\oplus\) 表示按位异或,\(popcnt(i)\) 表示 \(i\)\(1\) 的个数。

或卷积

我们要计算

\[C_i=\sum _{j\lor k=i} A_j\times B_k \]

我们尝试构造 \(A'\) 满足以下式子。

\[A'_i=\sum_{j\lor i=i} A_j \]

其本质上是子集的和。我们尝试将变换后的序列相乘发现它满足很好的性质:

\[\begin{align} A'\times B' &= \sum _{j\lor i=i}\sum_{k\lor i=i} A_jB_k\\ &= \sum_{i\lor(j\lor k)=i} A_jB_k \\ &= C' \end{align} \]

如何变换呢?子集和我们很熟悉了,可以用高维前缀和的技巧即 SOS DP 实现。给出实现,其中 inc 为带模加法,\(n\)\(2\) 的幂:

void fwt_or(int *a,int opt) {
	if(opt==1) {fu(i,0,n) fu(j,0,1<<n) if(j>>i&1) inc(a[j],a[j^1<<i]);}
	else {fu(i,0,n) fd(j,(1<<n)-1,0) if(j>>i&1) inc(a[j],mod-a[j^1<<i]);}
}

但是当然你可以使用网上的常规写法,自行百度。

与卷积

我们要计算

\[C_i=\sum _{j\land k=i} A_j\times B_k \]

类似或卷积构造 \(A'\)

\[A'_i=\sum _{j\land i=i} A_i \]

其本质上是超集的和。我们再证明一下它的正确性。

\[\begin{align} A'\times B' &= \sum _{j\land i=i}\sum_{k\land i=i} A_jB_k\\ &= \sum_{i\land(j\land k)} A_jB_k \\ &= C' \end{align} \]

超集和的实现一样是 SOS DP。实现如下:

void fwt_and(int *a,int opt) {
	if(opt==1) {fu(i,0,n) fd(j,(1<<n)-1,0) if(!(j>>i&1)) inc(a[j],a[j^1<<i]);}
	else {fu(i,0,n) fu(j,0,1<<n) if(!(j>>i&1)) inc(a[j],mod-a[j^1<<i]);}
}

但是当然你可以使用网上的常规写法,自行百度。

异或卷积

我们要计算

\[C_i=\sum _{j\oplus k=i} A_j\times B_k \]

异或卷积稍微麻烦一些,我们定义运算 \(i\otimes j=popcnt(i\land j)\bmod 2\)。它满足性质:

\[(x\otimes y)\oplus(x\otimes z)=x\otimes(y\oplus z) \]

证明分讨一下即可,自行百度。

我们定义一下变换。

\[A_i'=\sum _{i\otimes j=0} A_j-\sum_{i\otimes j=1} A_j \]

我们来证明一下卷积的正确性。

\[\begin{align} A'\times B' &= (\sum _{i\otimes j=0} A_j-\sum_{i\otimes j=1} A_j)(\sum _{i\otimes k=0} B_k-\sum_{i\otimes k=1} B_k) \\ &= (\sum _{i\otimes j=0}\sum_{i\otimes k=0}A_jB_k+\sum _{i\otimes j=1}\sum_{i\otimes k=1}A_jB_k)-(\sum _{i\otimes j=0}\sum_{i\otimes k=1}A_jB_k+\sum _{i\otimes j=1}\sum_{i\otimes k=0}A_jB_k) \\ &= \sum_{i\otimes(j\oplus k)=0}A_jB_k-\sum_ {i\otimes(j\oplus k)=1}A_jB_k \\ &= C' \end{align} \]

我们考虑怎么计算变换。有顺变换公式:

\[A'=merge(A'_0+A'_1,A'_0-A'_1) \]

逆变换:

\[A=\frac {merge(A_0+A_1,A_0-A_1)}2 \]

其中 \(merge\) 表示拼接,\(+,-\) 表示逐项相加减,我们枚举一个位数 \(i\)\(A_0\) 为第 \(i\) 位为 \(0\) 的子序列,\(A_1\) 为第 \(i\) 位为 \(1\) 的子序列。

实现如下:

void fwt_xor(int *a,int opt) {
	if(opt==-1) opt=pow1(2,mod-2);
	for(int i=1;i<1<<n;i<<=1) {
		for(int j=0;j<1<<n;j+=i<<1) {
			fu(k,j,j+i) {
				int t=a[k+i];
				a[k+i]=(a[k]+mod-a[k+i])%mod;
				inc(a[k],t);
			}
		}
		fu(j,0,1<<n) a[j]=(ll)a[j]*opt%mod;
	}
}

The End

posted @ 2025-02-13 21:29  dengchengyu  阅读(17)  评论(0)    收藏  举报