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

luogu P4717 【模板】快速莫比乌斯 / 沃尔什变换 (FMT / FWT)

给定长 \(2^n\) 的两个序列 \(a,b\),设:

\[c_k=\sum_{i\otimes j=k}a_ib_j \]

分别求出 \(\otimes=\operatorname{and},\operatorname{or},\operatorname{xor}\) 时的 \(c\)

引入

以下记序列长度 \(m=2^n\)\(n\) 为维数。

FMT/FWT 在 OI 中,实际上是在做 \(\mathcal O\left(n2^n\right)\)位运算卷积

但如果从集合的角度来理解,也是可以理解的。

FMT 常常用于或卷积、与卷积,而 FWT 用于异或卷积、同或卷积。

过程有点类似于 FFT/NTT,大概就是将原序列变换后直接乘,乘完了再做逆变换,从而避免 \(\mathcal O\left(m^2\right)\) 计算。

FMT

或卷积

考虑计算:

\[c_k=\sum_{i\operatorname{or}j=k}a_ib_j \]

考虑 \(i\operatorname{or}k=k,j\operatorname{or}k=k\implies(i\operatorname{or}j)\operatorname{or}k=k\)

于是对于序列 \(a\),构造 FMT 变换:

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

于是有:

\[\begin{aligned} \operatorname{FMT}(a)_k\cdot\operatorname{FMT}(b)_k&=\left(\sum_{i\operatorname{or}k=k}a_i\right)\left(\sum_{j\operatorname{or}k=k}b_j\right)\\ &=\sum_{i\operatorname{or}k=k}\sum_{j\operatorname{or}k=k}a_ib_j\\ &=\sum_{(i\operatorname{or}j)\operatorname{or}k=k}a_ib_j\\ &=\sum_{i\operatorname{or}k=k}c_i\\ &=\operatorname{FMT}(c)_k \end{aligned} \]

于是计算 \(\operatorname{FMT}(a)_k\cdot\operatorname{FMT}(b)_k=\operatorname{FMT}(c)_k\)\(\mathcal O(2^n)\) 的,再做一遍逆变换即可,即高维差分。

计算 \(\operatorname{FMT}(a)_i\) 可以用高维前缀和做到 \(\mathcal O\left(n2^n\right)\)


也可以考虑集合表示,\(i\subseteq k,j\subseteq k\implies i\cup j\implies k\)

\[\begin{aligned} c_k&=\sum_{i\cup j=k}a_ib_j\\ \operatorname{FMT}(a)_i&=\sum_{j\subseteq i}a_j \\ \operatorname{FMT}(a)_k\cdot\operatorname{FMT}(b)_k&=\left(\sum_{i\subseteq k}a_i\right)\left(\sum_{j\subseteq k}b_j\right)\\ &=\sum_{i\subseteq k}\sum_{j\subseteq k}a_ib_j\\ &=\sum_{(i\cup j)\subseteq k}a_ib_j\\ &=\sum_{i\subseteq k}c_i\\ &=\operatorname{FMT}(c)_k \end{aligned} \]

于是可以理解这就是求子集的和,做高维前缀和即可。对于逆变换,就是高维差分。

有趣的事情是,可以发现这里印证了学长的那句「莫比乌斯变换本质是高维差分」。

因为每次只对维度 \(i\) 考虑,所以从小到大遍历和从大到小不重要。

代码实现上,\(\textit{op}=1\) 为正变换,\(\textit{op}=-1\) 为逆变换。

void FMTOR(int a[],int n,int op){
	for(int i=0;i<n;i++){
		for(int j=0;j<(1<<n);j++){
			if(j&(1<<i)){
				a[j]=(a[j]+op*a[j^(1<<i)])%P;
			}
		}
	}
}

与卷积

考虑计算:

\[c_k=\sum_{i\operatorname{and}j=k}a_ib_j \]

类似的考虑 \(i\operatorname{and} k=k,j\operatorname{and}k=k\implies (i\operatorname{and}j)\operatorname{and}k=k\),构造:

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

证明是几乎一模一样:

\[\begin{aligned} \operatorname{FMT}(a)_k\cdot\operatorname{FMT}(b)_k&=\left(\sum_{i\operatorname{and}k=k}a_i\right)\left(\sum_{j\operatorname{and}k=k}b_j\right)\\ &=\sum_{i\operatorname{and}k=k}\sum_{j\operatorname{and}k=k}a_ib_j\\ &=\sum_{(i\operatorname{and}j)\operatorname{and}k=k}a_ib_j\\ &=\sum_{i\operatorname{and}k=k}c_i\\ &=\operatorname{FMT}(c)_k \end{aligned} \]

同样可以写集合:

\[\begin{aligned} c_k&=\sum_{i\cap j=k}a_ib_j\\ \operatorname{FMT}(a)_i&=\sum_{j\supseteq i}a_j \\ \operatorname{FMT}(a)_k\cdot\operatorname{FMT}(b)_k&=\left(\sum_{i\supseteq k}a_i\right)\left(\sum_{j\supseteq k}b_j\right)\\ &=\sum_{i\supseteq k}\sum_{j\supseteq k}a_ib_j\\ &=\sum_{(i\cup j)\supseteq k}a_ib_j\\ &=\sum_{i\supseteq k}c_i\\ &=\operatorname{FMT}(c)_k \end{aligned} \]

可以发现这就是求超集的和,也可以类似高维前缀和(其实是高维后缀和)。

void FMTAND(int a[],int n,int op){
	for(int i=0;i<n;i++){
		for(int j=0;j<(1<<n);j++){
			if(!(j&(1<<i))){
				a[j]=(a[j]+op*a[j^(1<<i)])%P;
			}
		}
	}
}

FWT

异或卷积

考虑求:

\[c_k=\sum_{i\operatorname{xor}j=k}a_ib_j \]

对于序列 \(a\),定义:

\[\operatorname{FWT}(a)_i=\sum_j(-1)^{\operatorname{popcount}(i\operatorname{and}j)}a_j \]

于是有:

\[\begin{aligned} \operatorname{FWT}(a)_k\cdot\operatorname{FWT}(b)_k&=\left(\sum_i(-1)^{\operatorname{popcount}(i\operatorname{and}k)}a_i\right)\left(\sum_j(-1)^{\operatorname{popcount}(j\operatorname{and}k)}b_j\right)\\ &=\sum_i\sum_j(-1)^{\operatorname{popcount}(i\operatorname{and}k)+\operatorname{popcount}(j\operatorname{and}k)}a_ib_j \end{aligned} \]

考虑到:

\[\operatorname{popcount}(i\operatorname{and}k)+\operatorname{popcount}(j\operatorname{and}k)\equiv\operatorname{popcount}((i\operatorname{xor}j)\operatorname{and}k)\pmod2 \]

不妨假定 \(i,j,k\in\set{0,1}\),把每一位分开考虑。

  • \(k=0\) 时,原式等价于 \(0+0\equiv0\pmod2\) 显然成立。
  • \(k=1\) 时,原式等价于 \(i+j\equiv((i+j)\bmod2)\pmod2\),因为 \(\operatorname{xor}\) 也是不进位加法。这显然成立。

于是有:

\[\begin{aligned} \operatorname{FWT}(a)_k\cdot\operatorname{FWT}(b)_k&=\left(\sum_i(-1)^{\operatorname{popcount}(i\operatorname{and}k)}a_i\right)\left(\sum_j(-1)^{\operatorname{popcount}(j\operatorname{and}k)}b_j\right)\\ &=\sum_i\sum_j(-1)^{\operatorname{popcount}((i\operatorname{xor}j)\operatorname{and}k)}a_ib_j\\ &=\sum_i(-1)^{\operatorname{popcount}(i\operatorname{and}k)}c_i\\ &=\operatorname{FWT}(c)_k \end{aligned} \]

考虑集合语言描述:

\[\begin{aligned} c_k&=\sum_{i\triangle j=k}a_ib_j\\ \operatorname{FWT}(a)_i&=\sum_j(-1)^{\vert i\cap j\vert}a_j\\ \operatorname{FWT}(a)_k\cdot\operatorname{FWT}(b)_k&=\left(\sum_i(-1)^{\vert i\cap k\vert}a_i\right)\left(\sum_j(-1)^{\vert j\cap k\vert}b_j\right)\\ &=\sum_i\sum_j(-1)^{\vert(i\triangle j)\cap k\vert}a_ib_j\\ &=\sum_i(-1)^{\vert i\cap k\vert}c_i\\ &=\operatorname{FWT}(c)_k \end{aligned} \]

于是这么构造是正确的,考虑如何实现 FWT 变换。

扫每一维 \(i\),设 \(j\operatorname{and}2^i=0,j\operatorname{or}2^i\)

那么对于正变换有:

\[(a_j,a_{j\operatorname{or}2^i})\leftarrow(a_j+a_{j\operatorname{or}2^i},a_j-a_{j\operatorname{or}2^i}) \]

考虑 \(j\)\(i\) 位为 \(0\),答案在 \(\operatorname{popcount}(k\operatorname{and} j)=0\) 的时候贡献为 \(1\)

否则指数增加了 \(1\),贡献为负数。

对于逆变换有:

\[(a_j,a_{j\operatorname{or}2^i})\leftarrow\left(\dfrac{a_j+a_{j\operatorname{or}2^i}}2,\dfrac{a_j-a_{j\operatorname{or}2^i}}2\right) \]

逆变换的时候要除以 \(2\) 剔除重复贡献。

void FWTXOR(int a[],int n,int op){
	for(int i=0;i<n;i++){
		for(int j=0;j<(1<<n);j++){
			if(!(j&(1<<i))){
				int x=a[j],y=a[j^(1<<i)];
				if(op==1){
					a[j]=(x+y)%P;
					a[j^(1<<i)]=(x-y)%P;
				}else{
					a[j]=1ll*(x+y)*inv2%P;
					a[j^(1<<i)]=1ll*(x-y)*inv2%P;
				}
			}
		}
	}
}

同或卷积

和异或卷积类似。把变换的时候 \(j,j\operatorname{or}2^i\) 交换一下即可。

void FWTXNOR(int a[],int n,int op){
	for(int i=0;i<n;i++){
		for(int j=0;j<(1<<n);j++){
			if(j&(1<<i)){
				int x=a[j],y=a[j^(1<<i)];
				if(op==1){
					a[j]=(x+y)%P;
					a[j^(1<<i)]=(x-y)%P;
				}else{
					a[j]=1ll*(x+y)*inv2%P;
					a[j^(1<<i)]=1ll*(x-y)*inv2%P;
				}
			}
		}
	}
}

子集卷积

luogu P6097 【模板】子集卷积

给定长 \(2^n\) 的序列 \(a,b\),求:

\[c_k=\sum_{i\operatorname{and}j=0\\i\operatorname{or}j=k}a_ib_j \]

将其视为集合:

\[c_k=\sum_{i\cap j=\varnothing\\i\cup j=k}a_ib_j \]

设或卷积运算 \(*\),有 \(c=a*b\)

子集卷积并没有像 FMT/FWT 那样的 \(\mathcal O\left(n2^n\right)\) 的简单方法。

\(i\cup j=k\) 的限制可以用 FMT 或卷积维护,但是处理不了 \(i\cap j=\varnothing\) 的限制。

考虑运算,若 \(i\cap j=\varnothing,i\cup j=k\),可以得到 \(\vert i\vert+\vert j\vert=\vert k\vert\)。并且 \(\vert i\vert+\vert j\vert=\vert k\vert\) 当且仅当 \(i\cap j=\varnothing\)

考虑强行升维,设 \(a_{i,s}\) 表示大小为 \(i\) 的集合 \(s\) 的值,\(b_{i,s},c_{i,s}\) 同理,答案即 \(c_{\vert s\vert,s}\)

因此有:

\[c_i=\sum_{k=0}^ia_i*b_{i-k} \]

具体而言,对 \(a_{i,s},b_{i,s}\) 进行 FMT 变换后,有:

\[c_{i,j}=\sum_{k=0}^ia_{i,j}b_{i-k,j} \]

于是可以 \(\mathcal O\left(n^22^n\right)\) 计算。

参考代码
//#include<bits/stdc++.h>
#include<algorithm> 
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=20,P=1e9+9;
int n,a[N+1][1<<N|1],b[N+1][1<<N|1],c[N+1][1<<N|1];
void FMTOR(int a[],int n,int op){
	for(int i=0;i<n;i++){
		for(int j=0;j<(1<<n);j++){
			if(j&(1<<i)){
				a[j]=(a[j]+op*a[j^(1<<i)])%P;
			}
		}
	}
}
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	cin>>n;
	for(int i=0;i<(1<<n);i++){
		cin>>a[__builtin_popcount(i)][i];
	}
	for(int i=0;i<(1<<n);i++){
		cin>>b[__builtin_popcount(i)][i];
	}
	for(int i=0;i<=n;i++){
		FMTOR(a[i],n,1);
		FMTOR(b[i],n,1);
	}
	for(int i=0;i<=n;i++){
		for(int k=0;k<=i;k++){
			for(int j=0;j<(1<<n);j++){
				c[i][j]=(c[i][j]+1ll*a[k][j]*b[i-k][j])%P;
			}
		}
	}
	for(int i=0;i<=n;i++){
		FMTOR(c[i],n,-1);
	}
	for(int i=0;i<(1<<n);i++){
		cout<<(c[__builtin_popcount(i)][i]+P)%P<<' ';
	}
	
	
	cout.flush(); 
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}
posted @ 2026-08-22 12:00  TH911  阅读(2)  评论(0)    收藏  举报