【模板】快速莫比乌斯/沃尔什变换 (FMT/FWT)
2025.2.7 添加内容
参考文献:题解 P4717 【【模板】快速莫比乌斯/沃尔什变换 (FMT/FWT)】 - 洛谷专栏
下文不区分 FWT 和 FMT 而统一称作 FWT。
题目描述
有两个长度为
其中
卷积运算律
这三种卷积本身具有交换律、结合律、分配律。
交换律、结合律由
注意什么叫分配律。例如有
这是因为
三种卷积
按位异或卷积
令
将
则有:
观察到:
记这两个为
所以:
相当于我们篡改了我们要卷的东西,然后计算答案的时候改回去变成对的。假设继续递归算了
做完了。实际代码实现会有点鬼畜。
复杂度
点击查看代码
const int P=998244353;
void red(LL&x){x=(x%P+P)%P;}
void fwt_xor(LL *f,int n,int op){
for(int len=2,k=1;len<=n;len<<=1,k<<=1){
for(int i=0;i<n;i+=len){
for(int j=0;j<k;j++){
LL ta=f[i+j],tb=f[i+j+k];
red(f[i+j]=(ta+tb)*op);
red(f[i+j+k]=(ta-tb)*op);
}
}
}
}
fwt_xor(a,n,1),fwt_xor(b,n,1),multiple(a,b,c,n),fwt_xor(c,n,499122177);
//multiple 是逐位相乘,499122177 是 2 的逆元
void fwt(mint f[], int n) {
for (int k = 1, len = 2; len <= n; len <<= 1, k <<= 1) {
for (int i = 0; i < n; i += len) {
for (int j = 0; j < k; j++) {
auto x = f[i + j], y = f[i + j + k];
f[i + j] = x + y, f[i + j + k] = x - y;
}
}
}
}
// 逆变换在最后需要对所有数除以 2^n,或者在每个 x+y 和 x-y 处都除以 2
按位或卷积
令
我们复读一遍上面的过程:因为
施蝴蝶变换:
点击查看代码
void fwt_or(LL *f,int n,int op){
for(int len=2,k=1;len<=n;len<<=1,k<<=1){
for(int i=0;i<n;i+=len){
for(int j=0;j<k;j++)
red(f[i+j+k]+=f[i+j]*op);
}
}
}
fwt_or(a,n,1),fwt_or(b,n,1),multiple(a,b,c,n),fwt_or(c,n,-1);
void fort(mint a[], int n, mint op) {
for (int k = 1, len = 2; len <= n; len <<= 1, k <<= 1) {
for (int i = 0; i < n; i += len) {
for (int j = 0; j < k; j++) {
a[i + j + k] += a[i + j] * op;
}
}
}
}
// 正变换传入 op=1,逆变换传入 op=-1
按位与卷积
这和按位或卷积是互相对称的,可以将结论和代码反过来。
点击查看代码
void fwt_and(LL *f,int n,int op){
for(int len=2,k=1;len<=n;len<<=1,k<<=1)
for(int i=0;i<n;i+=len){
for(int j=0;j<k;j++){
red(f[i+j]+=f[i+j+k]*op);
}
}
}
fwt_and(a,n,1),fwt_and(b,n,1),multiple(a,b,c,n),fwt_and(c,n,-1);
void fandt(mint a[], int n, mint op) {
for (int k = 1, len = 2; len <= n; len <<= 1, k <<= 1) {
for (int i = 0; i < n; i += len) {
for (int j = 0; j < k; j++) {
a[i + j] += a[i + j + k] * op;
}
}
}
}
// 正变换传入 op=1,逆变换传入 op=-1
线性性
矩阵形式
我们可以写出:
第一个式子不是很显然,但是你可以看代码发现它们是一样的。关于它们的逆运算(IFWT,也可以称作逆变换),你可以认为,FWT 是一个线性运算,我们将
对应到代码则更简单,xor 的情况在最后除掉(注意到 +=
改成 -=
。
也就是说:FWT 的变换事实上是左乘一个矩阵。矩阵为这种变换带来了线性性。
FWT 是系数转点值
你可以将一个数组 FWT 后的结果进行各种操作,好像把它当作一个 valarray
一样进行运算,最后再对其做 IFWT(也就是 FWT 的逆运算、逆变换)。
这是因为 FWT 实际上是将一个集合幂级数转换为
这是一个例子,我们对
fwt(f,1<<n,1);
for(int i=0;i<1<<n;i++) f[i]=qpow(f[i],m);
fwt(f,1<<n,-1);
快速修改系数
有时候我们需要修改原来的集合幂级数的某个系数,那么怎么对应到新的点值表示?非常简单,例如
按位独立性
按位独立性
FWT 的一个重要性质是:处理每一位的顺序是无关紧要的。这点与 FFT 不同,因为运算都是位运算。
这意味着每一位的位运算操作可以是不同的,有题目考察到了这一点:P5406 [THUPC 2019] 找树 - 洛谷 | 计算机科学教育新生态。做法是每一位分别去做对应的 FWT。
如果输入的
逆运算也是按位独立的,每一位是否做了 FWT 或者 IFWT 不会影响其它位。
如何单独对第 i >> b & 1
的,然后拿出 a[i]
和 a[i ^ (1 << b)]
这两项进行操作即可。
提取某一位、添加位、删除位
例如我们现在手上有点值,如何得知原来的系数中奇数项和偶数项是否相等,或者偶数项是否全为零?我们只需要单独对这一位做 IFWT,这样把原数组分成两份,这两份分别就是这一位为 由此你可以在
添加位的时候,首先进行位轮换为将要添加的位腾出位置。然后根据添加的这一位是全填
删除位的时候,单独对这一位做 IFWT,然后选择某种策略将其合并或删除,最后进行位轮换把它移出去。
实际代码不用这么麻烦,可以预先知道“单独对这一位做 FWT”的效果,少写一点东西。
子集卷积、集合幂级数相关问题
我建议你可以看看这篇文章
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/template-fwt.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话