快速沃尔什变换(FWT)
参考资料
实现
分治递归
- 按位或(or)
- 按位与(and)
- 按位异或(xor)
- 按位同或(xnor)
void fmt_or(ll *a,int n,int type)
{
if(n==1)return;
int mid=n>>1;
ll *a0=a,*a1=a+mid;
fmt_or(a0,mid,type);
fmt_or(a1,mid,type);
for(int i=0;i<mid;i++)a1[i]=(a1[i]+a0[i]*type+mod)%mod;
}
void fmt_and(ll *a,int n,int type)
{
if(n==1)return;
int mid=n>>1;
ll *a0=a,*a1=a+mid;
fmt_and(a0,mid,type);
fmt_and(a1,mid,type);
for(int i=0;i<mid;i++)a0[i]=(a0[i]+a1[i]*type+mod)%mod;
}
void fwt_xor(ll *a,int n,int type)
{
if(n==1)return;
int mid=n>>1;
ll *a0=a,*a1=a+mid;
fwt_xor(a0,mid,type);
fwt_xor(a1,mid,type);
ll t=type==1?1:mod+1>>1;
for(int i=0;i<mid;i++)
{
ll x=a0[i],y=a1[i];
a0[i]=(x+y)*t%mod;
a1[i]=(x-y+mod)*t%mod;
}
}
void fwt_xnor(ll *a,int n,int type)
{
if(n==1)return;
int mid=n>>1;
ll *a0=a,*a1=a+mid;
fwt_xnor(a0,mid,type);
fwt_xnor(a1,mid,type);
ll t=type==1?1:mod+1>>1;
for(int i=0;i<mid;i++)
{
ll x=a0[i],y=a1[i];
a0[i]=(y-x+mod)*t%mod;
a1[i]=(y+x)*t%mod;
}
}
倍增迭代
- 按位或(or)
- 按位与(and)
- 按位异或(xor)
- 按位同或(xnor)
void fmt_or(ll *a,int n,int type)
{
for(int k=1;k<n;k<<=1)
{
for(int i=0;i<n;i+=k<<1)
{
for(int j=0;j<k;j++)
{
a[i+j+k]=(a[i+j+k]+a[i+j]*type+mod)%mod;
}
}
}
}
void fmt_and(ll *a,int n,int type)
{
for(int k=1;k<n;k<<=1)
{
for(int i=0;i<n;i+=k<<1)
{
for(int j=0;j<k;j++)
{
a[i+j]=(a[i+j]+a[i+j+k]*type+mod)%mod;
}
}
}
}
void fwt_xor(ll *a,int n,int type)
{
ll t=type==1?1:mod+1>>1;
for(int k=1;k<n;k<<=1)
{
for(int i=0;i<n;i+=k<<1)
{
for(int j=0;j<k;j++)
{
ll x=a[i+j],y=a[i+j+k];
a[i+j]=(x+y)*t%mod;
a[i+j+k]=(x-y+mod)*t%mod;
}
}
}
}
void fwt_xnor(ll *a,int n,int type)
{
ll t=type==1?1:mod+1>>1;
for(int k=1;k<n;k<<=1)
{
for(int i=0;i<n;i+=k<<1)
{
for(int j=0;j<k;j++)
{
ll x=a[i+j],y=a[i+j+k];
a[i+j]=(y-x+mod)*t%mod;
a[i+j+k]=(y+x)*t%mod;
}
}
}
}
高维前缀和
- 按位或(or)
- 按位与(and)
void fmt_or(ll *a,int n,int type)
{
for(int i=1;i<n;i<<=1)
{
for(int j=0;j<n;j++)
{
if(i&j)a[j]=(a[j]+a[i^j]*type+mod)%mod;
}
}
}
void fmt_and(ll *a,int n,int type)
{
for(int i=1;i<n;i<<=1)
{
for(int j=0;j<n;j++)
{
if(!(i&j))a[j]=(a[j]+a[i^j]*type+mod)%mod;
}
}
}
例题
给定长度为 的两个序列 ,设:
分别当 是 时求出 。 给定两个长度为 的序列 ,定义: 分别在 是 时求序列 。 数据规模 记为: 位运算符号 记为: 快速傅里叶变换(FFT,Fast Fourier Transform)可以 计算 加法卷积(多项式乘法): 若将 加法()替换为 位运算(),得到 位运算卷积 该如何处理呢? 显然,直接枚举的时间复杂度为 ,因此需要引入 FMT 和 FWT。 快速莫比乌斯变换(FMT,Fast Möbius Transform)可以 计算 或卷积 和 与卷积: 快速沃尔什变换(FWT,Fast Walsh Transform)可以 计算 异或卷积 和 同或卷积: 有些资料中会将 FMT 和 FWT 统称为 FWT。 FMT 和 FWT 的思想与 FFT 类似。 首先 进行 正变换: 然后 逐位相乘(点积): 最后 进行 逆变换 得到结果: 这样总时间复杂度为 。 每个 二进制数 可以视为一个 集合,二进制位为 的位置表示对应元素属于该集合。 两个数 按位或 对应集合的 并集,而 按位与 对应 交集。 超集是补集的子集的补集,即超集和子集在补集意义上互为对偶。 简单来说,就是将 取反。因此子集和超集是 对称 的。 变换(Transform)是指根据特定规则,将一个对象(例如数、向量、函数、图形等)映射为另一个对象的过程。 变换的逆过程称为 逆变换(Inverse Transform),亦称 反演(Inversion)。 变换广泛出现于数学和算法领域,例如傅里叶变换、数论变换、莫比乌斯变换和沃尔什变换。 常见变换的命名方式: 考虑一个集合函数 ,其中 表示 的幂集,即 的所有子集构成的集合。 定义 子集和(Sum Over Subsets)和 超集和(Sum Over Supersets): 计算 子集和 和 超集和 的过程()称为 Zeta 变换(Zeta Transform)。 Zeta 的逆变换()称为 莫比乌斯反演(Möbius Inversion)。 可以通过 容斥原理 证明: 以上为子集的情形,超集的证明同理。 莫比乌斯反演也广泛出现于其他数学和算法领域,例如 数论中的莫比乌斯反演。 它们的共同点是在某种偏序上逆转累加关系以恢复原函数。 考虑如何构造变换规则。 若 (并集为 ),则 和 必为 的子集: 若 (交集为 ),则 和 必为 的超集: 我们可以构造: 则有: 以上两式分别对应 子集和 和 超集和,即 Zeta 变换 和 莫比乌斯反演。 考虑如何计算 Zeta 变换。 显然,直接枚举的时间复杂度为 。类似 FFT,可以考虑分治。 设序列 的前一半为 ,后一半为 。不难发现, 最高位都为 , 的最高位都为 。 那么, 的子集就是它本身的子集,而 满足条件的子集是它本身和 的子集: 其中, 表示将两个序列首尾相连(类似字符串拼接),加法()表示逐项相加。 这样我们就能在 的时间复杂度得到 和 。 既然 的子集是它本身, 的子集是 ,即可得到反演的递推式: 同理可得: 正变换和逆变换可以合并为一个函数:正变换取 ,逆变换取 。 综上,可用分治写出 和 的递归版本: 类似 FFT,可用倍增写出 和 的更高效(常数更小)的迭代版本: 一维序列 的 前缀和(Prefix Sum)序列 ,表示索引 不超过 它的元素之和。 假设有 个维度,每个维度的索引范围为 。 扩展到 高维前缀和(K-Dimensional Prefix Sum),就是各维度索引都 不超过 它的元素之和。 计算高维前缀和的常用方法有两种:容斥原理 和 逐维前缀和。 根据 容斥原理,可得: 但该方法的时间复杂度为 。 注意到, 维前缀和可视为 次逐维求和: 因此,每次只考虑一个维度,固定其余维度,求若干个一维前缀和。 对所有 个维度分别求和后,即得到 维前缀和。 该方法的时间复杂度为 。 当 ,每维只有 两种索引时,高维前缀和 就是 子集和。 同理,可以通过 高维后缀和(K-Dimensional Suffix Sum)得到 超集和。 所以,我们也可以通过 高维前/后缀和 求出 Zeta 变换。 时间复杂度为 。 类似地,对所有 个维度分别做差分后,可通过 高维差分 得到莫比乌斯反演。 我们可以写出 和 的高维前缀和版本: FMT 还可以用于计算 子集卷积: 第一个限制 ,可以通过 或卷积 轻松处理。 第二个限制 ,即没有公共元素,等价于 。 我们可以多开一个维度,记录 集合大小。具体令: 则有: 答案为: 时间复杂度为 。 考虑构造异或运算的变换。 若令 表示 中 数量的奇偶性,即: 其中 表示 二进制中 的个数。 则有: 我们可以构造: 则有: 而以上式子就是 沃尔什变换。 我们依然考虑分治。 设序列 的前一半为 ,后一半为 。不难发现, 最高位都为 , 的最高位都为 。 当索引 的最高位为 时,无论与最高位为 还是 的 进行 运算,由于 ,奇偶性不变,因此这一部分为两者之和: 当索引 的最高位为 时,与最高位为 的 运算结果为 (奇偶性不变),与最高位为 的 运算结果为 (奇偶性翻转),因此这一部分为两者之差: 综上,可得: 逆变换易得: 同理,将 定义中的 替换为 ,即可得到同或: 正变换和逆变换可以合并为一个函数:正变换取 ,逆变换取 。 注意:在模意义下, 表示 的乘法逆元,即 。 综上,可用分治写出 和 的递归版本: 同理,可用倍增写出 和 更高效(常数更小)的迭代版本:Solution
参考资料
题意简述
解题思路
约定
引入
简介
思想
基础知识
位运算 & 集合
变换 & 反演
Zeta 变换
莫比乌斯反演
快速莫比乌斯变换(FMT)
原理
分治递归
正变换
逆变换
按位与
代码实现
void fmt_or(ll *a,int n,int type)
{
if(n==1)return;
int mid=n>>1;
ll *a0=a,*a1=a+mid;
fmt_or(a0,mid,type);
fmt_or(a1,mid,type);
for(int i=0;i<mid;i++)a1[i]=(a1[i]+a0[i]*type+mod)%mod;
}void fmt_and(ll *a,int n,int type)
{
if(n==1)return;
int mid=n>>1;
ll *a0=a,*a1=a+mid;
fmt_and(a0,mid,type);
fmt_and(a1,mid,type);
for(int i=0;i<mid;i++)a0[i]=(a0[i]+a1[i]*type+mod)%mod;
}倍增迭代
void fmt_or(ll *a,int n,int type)
{
for(int k=1;k<n;k<<=1)
{
for(int i=0;i<n;i+=k<<1)
{
for(int j=0;j<k;j++)
{
a[i+j+k]=(a[i+j+k]+a[i+j]*type+mod)%mod;
}
}
}
}void fmt_and(ll *a,int n,int type)
{
for(int k=1;k<n;k<<=1)
{
for(int i=0;i<n;i+=k<<1)
{
for(int j=0;j<k;j++)
{
a[i+j]=(a[i+j]+a[i+j+k]*type+mod)%mod;
}
}
}
}高维前缀和
前缀和
高维前缀和
容斥原理
逐维前缀和
正变换
逆变换
代码实现
void fmt_or(ll *a,int n,int type)
{
for(int i=1;i<n;i<<=1)
{
for(int j=0;j<n;j++)
{
if(i&j)a[j]=(a[j]+a[i^j]*type+mod)%mod;
}
}
}void fmt_and(ll *a,int n,int type)
{
for(int i=1;i<n;i<<=1)
{
for(int j=0;j<n;j++)
{
if(!(i&j))a[j]=(a[j]+a[i^j]*type+mod)%mod;
}
}
}子集卷积
快速沃尔什变换(FWT)
原理
分治递归
正变换
逆变换
按位同或
代码实现
void fwt_xor(ll *a,int n,int type)
{
if(n==1)return;
int mid=n>>1;
ll *a0=a,*a1=a+mid;
fwt_xor(a0,mid,type);
fwt_xor(a1,mid,type);
ll t=type==1?1:mod+1>>1;
for(int i=0;i<mid;i++)
{
ll x=a0[i],y=a1[i];
a0[i]=(x+y)*t%mod;
a1[i]=(x-y+mod)*t%mod;
}
}void fwt_xnor(ll *a,int n,int type)
{
if(n==1)return;
int mid=n>>1;
ll *a0=a,*a1=a+mid;
fwt_xnor(a0,mid,type);
fwt_xnor(a1,mid,type);
ll t=type==1?1:mod+1>>1;
for(int i=0;i<mid;i++)
{
ll x=a0[i],y=a1[i];
a0[i]=(y-x+mod)*t%mod;
a1[i]=(y+x)*t%mod;
}
}倍增迭代
void fwt_xor(ll *a,int n,int type)
{
ll t=type==1?1:mod+1>>1;
for(int k=1;k<n;k<<=1)
{
for(int i=0;i<n;i+=k<<1)
{
for(int j=0;j<k;j++)
{
ll x=a[i+j],y=a[i+j+k];
a[i+j]=(x+y)*t%mod;
a[i+j+k]=(x-y+mod)*t%mod;
}
}
}
}void fwt_xnor(ll *a,int n,int type)
{
ll t=type==1?1:mod+1>>1;
for(int k=1;k<n;k<<=1)
{
for(int i=0;i<n;i+=k<<1)
{
for(int j=0;j<k;j++)
{
ll x=a[i+j],y=a[i+j+k];
a[i+j]=(y-x+mod)*t%mod;
a[i+j+k]=(y+x)*t%mod;
}
}
}
}性能分析
-std=c++17 -O2;std::chrono::high_resolution_clock,单位为 ;std::mt19937_64,生成 个随机整数;类型/算法 倍增迭代 分治递归 高维前缀和 或卷积() 与卷积() 异或卷积() – 同或卷积() – 参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int mod=998244353;
const int N=20;
void fmt_or(ll *a,int n,int type)
{
for(int k=1;k<n;k<<=1)
{
for(int i=0;i<n;i+=k<<1)
{
for(int j=0;j<k;j++)
{
a[i+j+k]=(a[i+j+k]+a[i+j]*type+mod)%mod;
}
}
}
}
void fmt_and(ll *a,int n,int type)
{
for(int k=1;k<n;k<<=1)
{
for(int i=0;i<n;i+=k<<1)
{
for(int j=0;j<k;j++)
{
a[i+j]=(a[i+j]+a[i+j+k]*type+mod)%mod;
}
}
}
}
void fwt_xor(ll *a,int n,int type)
{
ll t=type==1?1:mod+1>>1;
for(int k=1;k<n;k<<=1)
{
for(int i=0;i<n;i+=k<<1)
{
for(int j=0;j<k;j++)
{
ll x=a[i+j],y=a[i+j+k];
a[i+j]=(x+y)*t%mod;
a[i+j+k]=(x-y+mod)*t%mod;
}
}
}
}
ll A[1<<N],B[1<<N],a[1<<N],b[1<<N];
void calc(auto f,int n)
{
for(int i=0;i<n;i++){a[i]=A[i];b[i]=B[i];}
f(a,n,1);
f(b,n,1);
for(int i=0;i<n;i++)a[i]=a[i]*b[i]%mod;
f(a,n,-1);
for(int i=0;i<n;i++)cout<<a[i]<<' ';
cout<<'\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin>>n;
n=1<<n;
for(int i=0;i<n;i++)cin>>A[i];
for(int i=0;i<n;i++)cin>>B[i];
calc(fmt_or,n);
calc(fmt_and,n);
calc(fwt_xor,n);
return 0;
}
给定两个长度为 的序列 和 ,你需要求出一个序列 ,其中 满足:
其中表示按位或,表示按位与。
答案对 取模。Code (3)
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int mod=1e9+9;
const int N=20;
void fmt(ll *a,int n,int type)
{
for(int k=1;k<n;k<<=1)
{
for(int i=0;i<n;i+=k<<1)
{
for(int j=0;j<k;j++)
{
a[i+j+k]=(a[i+j+k]+a[i+j]*type+mod)%mod;
}
}
}
}
ll a[N+1][1<<N],b[N+1][1<<N],c[N+1][1<<N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin>>n;
int m=1<<n;
for(int i=0;i<m;i++)cin>>a[__builtin_popcount(i)][i];
for(int i=0;i<m;i++)cin>>b[__builtin_popcount(i)][i];
for(int i=0;i<=n;i++){fmt(a[i],m,1);fmt(b[i],m,1);}
for(int i=0;i<=n;i++)
{
for(int j=0;j<=i;j++)
{
for(int k=0;k<m;k++)
{
c[i][k]=(c[i][k]+a[j][k]*b[i-j][k])%mod;
}
}
}
for(int i=0;i<=n;i++)fmt(c[i],m,-1);
for(int i=0;i<m;i++)cout<<c[__builtin_popcount(i)][i]<<' ';
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int mod=1e9+9;
const int N=20;
void fmt(ll *a,int n,int type)
{
if(n==1)return;
int mid=n>>1;
ll *a0=a,*a1=a+mid;
fmt(a0,mid,type);
fmt(a1,mid,type);
for(int i=0;i<mid;i++)a1[i]=(a1[i]+a0[i]*type+mod)%mod;
}
ll a[N+1][1<<N],b[N+1][1<<N],c[N+1][1<<N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin>>n;
int m=1<<n;
for(int i=0;i<m;i++)cin>>a[__builtin_popcount(i)][i];
for(int i=0;i<m;i++)cin>>b[__builtin_popcount(i)][i];
for(int i=0;i<=n;i++){fmt(a[i],m,1);fmt(b[i],m,1);}
for(int i=0;i<=n;i++)
{
for(int j=0;j<=i;j++)
{
for(int k=0;k<m;k++)
{
c[i][k]=(c[i][k]+a[j][k]*b[i-j][k])%mod;
}
}
}
for(int i=0;i<=n;i++)fmt(c[i],m,-1);
for(int i=0;i<m;i++)cout<<c[__builtin_popcount(i)][i]<<' ';
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int mod=1e9+9;
const int N=20;
void fmt(ll *a,int n,int type)
{
for(int i=1;i<n;i<<=1)
{
for(int j=0;j<n;j++)
{
if(i&j)a[j]=(a[j]+a[i^j]*type+mod)%mod;
}
}
}
ll a[N+1][1<<N],b[N+1][1<<N],c[N+1][1<<N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin>>n;
int m=1<<n;
for(int i=0;i<m;i++)cin>>a[__builtin_popcount(i)][i];
for(int i=0;i<m;i++)cin>>b[__builtin_popcount(i)][i];
for(int i=0;i<=n;i++){fmt(a[i],m,1);fmt(b[i],m,1);}
for(int i=0;i<=n;i++)
{
for(int j=0;j<=i;j++)
{
for(int k=0;k<m;k++)
{
c[i][k]=(c[i][k]+a[j][k]*b[i-j][k])%mod;
}
}
}
for(int i=0;i<=n;i++)fmt(c[i],m,-1);
for(int i=0;i<m;i++)cout<<c[__builtin_popcount(i)][i]<<' ';
return 0;
}